diff options
Diffstat (limited to 'platform/darwin/test')
22 files changed, 3303 insertions, 2836 deletions
diff --git a/platform/darwin/test/MGLBackgroundStyleLayerTests.mm b/platform/darwin/test/MGLBackgroundStyleLayerTests.mm index c96a4fe7fa..e7c2982413 100644 --- a/platform/darwin/test/MGLBackgroundStyleLayerTests.mm +++ b/platform/darwin/test/MGLBackgroundStyleLayerTests.mm @@ -31,39 +31,44 @@ { XCTAssertTrue(rawLayer->getBackgroundColor().isUndefined(), @"background-color should be unset initially."); - MGLStyleValue<MGLColor *> *defaultStyleValue = layer.backgroundColor; + NSExpression *defaultExpression = layer.backgroundColor; - MGLStyleValue<MGLColor *> *constantStyleValue = [MGLStyleValue<MGLColor *> valueWithRawValue:[MGLColor redColor]]; - layer.backgroundColor = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + layer.backgroundColor = constantExpression; mbgl::style::PropertyValue<mbgl::Color> propertyValue = { { 1, 0, 0, 1 } }; XCTAssertEqual(rawLayer->getBackgroundColor(), propertyValue, - @"Setting backgroundColor to a constant value should update background-color."); - XCTAssertEqualObjects(layer.backgroundColor, constantStyleValue, - @"backgroundColor should round-trip constant values."); - - MGLStyleValue<MGLColor *> * functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.backgroundColor = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::Color> intervalStops = { {{18, { 1, 0, 0, 1 }}} }; + @"Setting backgroundColor to a constant value expression should update background-color."); + XCTAssertEqualObjects(layer.backgroundColor, constantExpression, + @"backgroundColor should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.backgroundColor = functionExpression; + + mbgl::style::IntervalStops<mbgl::Color> intervalStops = {{ + { -INFINITY, { 1, 0, 0, 1 } }, + { 18, { 1, 0, 0, 1 } }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::Color> { intervalStops }; XCTAssertEqual(rawLayer->getBackgroundColor(), propertyValue, - @"Setting backgroundColor to a camera function should update background-color."); - XCTAssertEqualObjects(layer.backgroundColor, functionStyleValue, - @"backgroundColor should round-trip camera functions."); + @"Setting backgroundColor to a camera expression should update background-color."); + XCTAssertEqualObjects(layer.backgroundColor, functionExpression, + @"backgroundColor should round-trip camera expressions."); layer.backgroundColor = nil; XCTAssertTrue(rawLayer->getBackgroundColor().isUndefined(), @"Unsetting backgroundColor should return background-color to the default value."); - XCTAssertEqualObjects(layer.backgroundColor, defaultStyleValue, + XCTAssertEqualObjects(layer.backgroundColor, defaultExpression, @"backgroundColor should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.backgroundColor = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.backgroundColor = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.backgroundColor = functionExpression, NSException, NSInvalidArgumentException, @"MGLBackgroundLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.backgroundColor = functionExpression, NSException, NSInvalidArgumentException, @"MGLBackgroundLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); // Transition property test layer.backgroundColorTransition = transitionTest; auto toptions = rawLayer->getBackgroundColorTransition(); @@ -79,39 +84,44 @@ { XCTAssertTrue(rawLayer->getBackgroundOpacity().isUndefined(), @"background-opacity should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.backgroundOpacity; + NSExpression *defaultExpression = layer.backgroundOpacity; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.backgroundOpacity = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.backgroundOpacity = constantExpression; mbgl::style::PropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getBackgroundOpacity(), propertyValue, - @"Setting backgroundOpacity to a constant value should update background-opacity."); - XCTAssertEqualObjects(layer.backgroundOpacity, constantStyleValue, - @"backgroundOpacity should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.backgroundOpacity = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting backgroundOpacity to a constant value expression should update background-opacity."); + XCTAssertEqualObjects(layer.backgroundOpacity, constantExpression, + @"backgroundOpacity should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.backgroundOpacity = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getBackgroundOpacity(), propertyValue, - @"Setting backgroundOpacity to a camera function should update background-opacity."); - XCTAssertEqualObjects(layer.backgroundOpacity, functionStyleValue, - @"backgroundOpacity should round-trip camera functions."); + @"Setting backgroundOpacity to a camera expression should update background-opacity."); + XCTAssertEqualObjects(layer.backgroundOpacity, functionExpression, + @"backgroundOpacity should round-trip camera expressions."); layer.backgroundOpacity = nil; XCTAssertTrue(rawLayer->getBackgroundOpacity().isUndefined(), @"Unsetting backgroundOpacity should return background-opacity to the default value."); - XCTAssertEqualObjects(layer.backgroundOpacity, defaultStyleValue, + XCTAssertEqualObjects(layer.backgroundOpacity, defaultExpression, @"backgroundOpacity should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.backgroundOpacity = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.backgroundOpacity = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.backgroundOpacity = functionExpression, NSException, NSInvalidArgumentException, @"MGLBackgroundLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.backgroundOpacity = functionExpression, NSException, NSInvalidArgumentException, @"MGLBackgroundLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); // Transition property test layer.backgroundOpacityTransition = transitionTest; auto toptions = rawLayer->getBackgroundOpacityTransition(); @@ -127,39 +137,44 @@ { XCTAssertTrue(rawLayer->getBackgroundPattern().isUndefined(), @"background-pattern should be unset initially."); - MGLStyleValue<NSString *> *defaultStyleValue = layer.backgroundPattern; + NSExpression *defaultExpression = layer.backgroundPattern; - MGLStyleValue<NSString *> *constantStyleValue = [MGLStyleValue<NSString *> valueWithRawValue:@"Background Pattern"]; - layer.backgroundPattern = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"'Background Pattern'"]; + layer.backgroundPattern = constantExpression; mbgl::style::PropertyValue<std::string> propertyValue = { "Background Pattern" }; XCTAssertEqual(rawLayer->getBackgroundPattern(), propertyValue, - @"Setting backgroundPattern to a constant value should update background-pattern."); - XCTAssertEqualObjects(layer.backgroundPattern, constantStyleValue, - @"backgroundPattern should round-trip constant values."); - - MGLStyleValue<NSString *> * functionStyleValue = [MGLStyleValue<NSString *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.backgroundPattern = functionStyleValue; - - mbgl::style::IntervalStops<std::string> intervalStops = { {{18, "Background Pattern"}} }; + @"Setting backgroundPattern to a constant value expression should update background-pattern."); + XCTAssertEqualObjects(layer.backgroundPattern, constantExpression, + @"backgroundPattern should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"'Background Pattern'"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.backgroundPattern = functionExpression; + + mbgl::style::IntervalStops<std::string> intervalStops = {{ + { -INFINITY, "Background Pattern" }, + { 18, "Background Pattern" }, + }}; propertyValue = mbgl::style::CameraFunction<std::string> { intervalStops }; XCTAssertEqual(rawLayer->getBackgroundPattern(), propertyValue, - @"Setting backgroundPattern to a camera function should update background-pattern."); - XCTAssertEqualObjects(layer.backgroundPattern, functionStyleValue, - @"backgroundPattern should round-trip camera functions."); + @"Setting backgroundPattern to a camera expression should update background-pattern."); + XCTAssertEqualObjects(layer.backgroundPattern, functionExpression, + @"backgroundPattern should round-trip camera expressions."); layer.backgroundPattern = nil; XCTAssertTrue(rawLayer->getBackgroundPattern().isUndefined(), @"Unsetting backgroundPattern should return background-pattern to the default value."); - XCTAssertEqualObjects(layer.backgroundPattern, defaultStyleValue, + XCTAssertEqualObjects(layer.backgroundPattern, defaultExpression, @"backgroundPattern should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSString *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.backgroundPattern = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSString *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.backgroundPattern = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.backgroundPattern = functionExpression, NSException, NSInvalidArgumentException, @"MGLBackgroundLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.backgroundPattern = functionExpression, NSException, NSInvalidArgumentException, @"MGLBackgroundLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); // Transition property test layer.backgroundPatternTransition = transitionTest; auto toptions = rawLayer->getBackgroundPatternTransition(); diff --git a/platform/darwin/test/MGLCircleStyleLayerTests.mm b/platform/darwin/test/MGLCircleStyleLayerTests.mm index c0c503153a..7677344580 100644 --- a/platform/darwin/test/MGLCircleStyleLayerTests.mm +++ b/platform/darwin/test/MGLCircleStyleLayerTests.mm @@ -52,40 +52,44 @@ { XCTAssertTrue(rawLayer->getCircleBlur().isUndefined(), @"circle-blur should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.circleBlur; + NSExpression *defaultExpression = layer.circleBlur; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.circleBlur = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.circleBlur = constantExpression; mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getCircleBlur(), propertyValue, - @"Setting circleBlur to a constant value should update circle-blur."); - XCTAssertEqualObjects(layer.circleBlur, constantStyleValue, - @"circleBlur should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.circleBlur = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting circleBlur to a constant value expression should update circle-blur."); + XCTAssertEqualObjects(layer.circleBlur, constantExpression, + @"circleBlur should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.circleBlur = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getCircleBlur(), propertyValue, - @"Setting circleBlur to a camera function should update circle-blur."); - XCTAssertEqualObjects(layer.circleBlur, functionStyleValue, - @"circleBlur should round-trip camera functions."); + @"Setting circleBlur to a camera expression should update circle-blur."); + XCTAssertEqualObjects(layer.circleBlur, functionExpression, + @"circleBlur should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.circleBlur = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@18: constantExpression}]; + layer.circleBlur = functionExpression; mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getCircleBlur(), propertyValue, - @"Setting circleBlur to a source function should update circle-blur."); - XCTAssertEqualObjects(layer.circleBlur, functionStyleValue, - @"circleBlur should round-trip source functions."); + @"Setting circleBlur to a data expression should update circle-blur."); + XCTAssertEqualObjects(layer.circleBlur, functionExpression, + @"circleBlur should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.circleBlur = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + layer.circleBlur = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -93,15 +97,15 @@ propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getCircleBlur(), propertyValue, - @"Setting circleBlur to a composite function should update circle-blur."); - XCTAssertEqualObjects(layer.circleBlur, functionStyleValue, - @"circleBlur should round-trip composite functions."); + @"Setting circleBlur to a camera-data expression should update circle-blur."); + XCTAssertEqualObjects(layer.circleBlur, functionExpression, + @"circleBlur should round-trip camera-data expressions."); layer.circleBlur = nil; XCTAssertTrue(rawLayer->getCircleBlur().isUndefined(), @"Unsetting circleBlur should return circle-blur to the default value."); - XCTAssertEqualObjects(layer.circleBlur, defaultStyleValue, + XCTAssertEqualObjects(layer.circleBlur, defaultExpression, @"circleBlur should return the default value after being unset."); // Transition property test layer.circleBlurTransition = transitionTest; @@ -118,40 +122,44 @@ { XCTAssertTrue(rawLayer->getCircleColor().isUndefined(), @"circle-color should be unset initially."); - MGLStyleValue<MGLColor *> *defaultStyleValue = layer.circleColor; + NSExpression *defaultExpression = layer.circleColor; - MGLStyleValue<MGLColor *> *constantStyleValue = [MGLStyleValue<MGLColor *> valueWithRawValue:[MGLColor redColor]]; - layer.circleColor = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + layer.circleColor = constantExpression; mbgl::style::DataDrivenPropertyValue<mbgl::Color> propertyValue = { { 1, 0, 0, 1 } }; XCTAssertEqual(rawLayer->getCircleColor(), propertyValue, - @"Setting circleColor to a constant value should update circle-color."); - XCTAssertEqualObjects(layer.circleColor, constantStyleValue, - @"circleColor should round-trip constant values."); - - MGLStyleValue<MGLColor *> * functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.circleColor = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::Color> intervalStops = { {{18, { 1, 0, 0, 1 }}} }; + @"Setting circleColor to a constant value expression should update circle-color."); + XCTAssertEqualObjects(layer.circleColor, constantExpression, + @"circleColor should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.circleColor = functionExpression; + + mbgl::style::IntervalStops<mbgl::Color> intervalStops = {{ + { -INFINITY, { 1, 0, 0, 1 } }, + { 18, { 1, 0, 0, 1 } }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::Color> { intervalStops }; XCTAssertEqual(rawLayer->getCircleColor(), propertyValue, - @"Setting circleColor to a camera function should update circle-color."); - XCTAssertEqualObjects(layer.circleColor, functionStyleValue, - @"circleColor should round-trip camera functions."); + @"Setting circleColor to a camera expression should update circle-color."); + XCTAssertEqualObjects(layer.circleColor, functionExpression, + @"circleColor should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.circleColor = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@18: constantExpression}]; + layer.circleColor = functionExpression; mbgl::style::ExponentialStops<mbgl::Color> exponentialStops = { {{18, { 1, 0, 0, 1 }}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<mbgl::Color> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getCircleColor(), propertyValue, - @"Setting circleColor to a source function should update circle-color."); - XCTAssertEqualObjects(layer.circleColor, functionStyleValue, - @"circleColor should round-trip source functions."); + @"Setting circleColor to a data expression should update circle-color."); + XCTAssertEqualObjects(layer.circleColor, functionExpression, + @"circleColor should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.circleColor = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + layer.circleColor = functionExpression; std::map<float, mbgl::Color> innerStops { {18, { 1, 0, 0, 1 }} }; mbgl::style::CompositeExponentialStops<mbgl::Color> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -159,15 +167,15 @@ propertyValue = mbgl::style::CompositeFunction<mbgl::Color> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getCircleColor(), propertyValue, - @"Setting circleColor to a composite function should update circle-color."); - XCTAssertEqualObjects(layer.circleColor, functionStyleValue, - @"circleColor should round-trip composite functions."); + @"Setting circleColor to a camera-data expression should update circle-color."); + XCTAssertEqualObjects(layer.circleColor, functionExpression, + @"circleColor should round-trip camera-data expressions."); layer.circleColor = nil; XCTAssertTrue(rawLayer->getCircleColor().isUndefined(), @"Unsetting circleColor should return circle-color to the default value."); - XCTAssertEqualObjects(layer.circleColor, defaultStyleValue, + XCTAssertEqualObjects(layer.circleColor, defaultExpression, @"circleColor should return the default value after being unset."); // Transition property test layer.circleColorTransition = transitionTest; @@ -184,40 +192,44 @@ { XCTAssertTrue(rawLayer->getCircleOpacity().isUndefined(), @"circle-opacity should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.circleOpacity; + NSExpression *defaultExpression = layer.circleOpacity; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.circleOpacity = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.circleOpacity = constantExpression; mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getCircleOpacity(), propertyValue, - @"Setting circleOpacity to a constant value should update circle-opacity."); - XCTAssertEqualObjects(layer.circleOpacity, constantStyleValue, - @"circleOpacity should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.circleOpacity = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting circleOpacity to a constant value expression should update circle-opacity."); + XCTAssertEqualObjects(layer.circleOpacity, constantExpression, + @"circleOpacity should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.circleOpacity = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getCircleOpacity(), propertyValue, - @"Setting circleOpacity to a camera function should update circle-opacity."); - XCTAssertEqualObjects(layer.circleOpacity, functionStyleValue, - @"circleOpacity should round-trip camera functions."); + @"Setting circleOpacity to a camera expression should update circle-opacity."); + XCTAssertEqualObjects(layer.circleOpacity, functionExpression, + @"circleOpacity should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.circleOpacity = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@18: constantExpression}]; + layer.circleOpacity = functionExpression; mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getCircleOpacity(), propertyValue, - @"Setting circleOpacity to a source function should update circle-opacity."); - XCTAssertEqualObjects(layer.circleOpacity, functionStyleValue, - @"circleOpacity should round-trip source functions."); + @"Setting circleOpacity to a data expression should update circle-opacity."); + XCTAssertEqualObjects(layer.circleOpacity, functionExpression, + @"circleOpacity should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.circleOpacity = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + layer.circleOpacity = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -225,15 +237,15 @@ propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getCircleOpacity(), propertyValue, - @"Setting circleOpacity to a composite function should update circle-opacity."); - XCTAssertEqualObjects(layer.circleOpacity, functionStyleValue, - @"circleOpacity should round-trip composite functions."); + @"Setting circleOpacity to a camera-data expression should update circle-opacity."); + XCTAssertEqualObjects(layer.circleOpacity, functionExpression, + @"circleOpacity should round-trip camera-data expressions."); layer.circleOpacity = nil; XCTAssertTrue(rawLayer->getCircleOpacity().isUndefined(), @"Unsetting circleOpacity should return circle-opacity to the default value."); - XCTAssertEqualObjects(layer.circleOpacity, defaultStyleValue, + XCTAssertEqualObjects(layer.circleOpacity, defaultExpression, @"circleOpacity should return the default value after being unset."); // Transition property test layer.circleOpacityTransition = transitionTest; @@ -250,79 +262,88 @@ { XCTAssertTrue(rawLayer->getCirclePitchAlignment().isUndefined(), @"circle-pitch-alignment should be unset initially."); - MGLStyleValue<NSValue *> *defaultStyleValue = layer.circlePitchAlignment; + NSExpression *defaultExpression = layer.circlePitchAlignment; - MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue:[NSValue valueWithMGLCirclePitchAlignment:MGLCirclePitchAlignmentViewport]]; - layer.circlePitchAlignment = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"'viewport'"]; + layer.circlePitchAlignment = constantExpression; mbgl::style::PropertyValue<mbgl::style::AlignmentType> propertyValue = { mbgl::style::AlignmentType::Viewport }; XCTAssertEqual(rawLayer->getCirclePitchAlignment(), propertyValue, - @"Setting circlePitchAlignment to a constant value should update circle-pitch-alignment."); - XCTAssertEqualObjects(layer.circlePitchAlignment, constantStyleValue, - @"circlePitchAlignment should round-trip constant values."); - - MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.circlePitchAlignment = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::style::AlignmentType> intervalStops = { {{18, mbgl::style::AlignmentType::Viewport}} }; + @"Setting circlePitchAlignment to a constant value expression should update circle-pitch-alignment."); + XCTAssertEqualObjects(layer.circlePitchAlignment, constantExpression, + @"circlePitchAlignment should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"'viewport'"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.circlePitchAlignment = functionExpression; + + mbgl::style::IntervalStops<mbgl::style::AlignmentType> intervalStops = {{ + { -INFINITY, mbgl::style::AlignmentType::Viewport }, + { 18, mbgl::style::AlignmentType::Viewport }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::style::AlignmentType> { intervalStops }; XCTAssertEqual(rawLayer->getCirclePitchAlignment(), propertyValue, - @"Setting circlePitchAlignment to a camera function should update circle-pitch-alignment."); - XCTAssertEqualObjects(layer.circlePitchAlignment, functionStyleValue, - @"circlePitchAlignment should round-trip camera functions."); + @"Setting circlePitchAlignment to a camera expression should update circle-pitch-alignment."); + XCTAssertEqualObjects(layer.circlePitchAlignment, functionExpression, + @"circlePitchAlignment should round-trip camera expressions."); layer.circlePitchAlignment = nil; XCTAssertTrue(rawLayer->getCirclePitchAlignment().isUndefined(), @"Unsetting circlePitchAlignment should return circle-pitch-alignment to the default value."); - XCTAssertEqualObjects(layer.circlePitchAlignment, defaultStyleValue, + XCTAssertEqualObjects(layer.circlePitchAlignment, defaultExpression, @"circlePitchAlignment should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.circlePitchAlignment = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.circlePitchAlignment = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.circlePitchAlignment = functionExpression, NSException, NSInvalidArgumentException, @"MGLCircleLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.circlePitchAlignment = functionExpression, NSException, NSInvalidArgumentException, @"MGLCircleLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // circle-radius { XCTAssertTrue(rawLayer->getCircleRadius().isUndefined(), @"circle-radius should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.circleRadius; + NSExpression *defaultExpression = layer.circleRadius; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.circleRadius = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.circleRadius = constantExpression; mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getCircleRadius(), propertyValue, - @"Setting circleRadius to a constant value should update circle-radius."); - XCTAssertEqualObjects(layer.circleRadius, constantStyleValue, - @"circleRadius should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.circleRadius = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting circleRadius to a constant value expression should update circle-radius."); + XCTAssertEqualObjects(layer.circleRadius, constantExpression, + @"circleRadius should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.circleRadius = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getCircleRadius(), propertyValue, - @"Setting circleRadius to a camera function should update circle-radius."); - XCTAssertEqualObjects(layer.circleRadius, functionStyleValue, - @"circleRadius should round-trip camera functions."); + @"Setting circleRadius to a camera expression should update circle-radius."); + XCTAssertEqualObjects(layer.circleRadius, functionExpression, + @"circleRadius should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.circleRadius = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@18: constantExpression}]; + layer.circleRadius = functionExpression; mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getCircleRadius(), propertyValue, - @"Setting circleRadius to a source function should update circle-radius."); - XCTAssertEqualObjects(layer.circleRadius, functionStyleValue, - @"circleRadius should round-trip source functions."); + @"Setting circleRadius to a data expression should update circle-radius."); + XCTAssertEqualObjects(layer.circleRadius, functionExpression, + @"circleRadius should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.circleRadius = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + layer.circleRadius = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -330,15 +351,15 @@ propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getCircleRadius(), propertyValue, - @"Setting circleRadius to a composite function should update circle-radius."); - XCTAssertEqualObjects(layer.circleRadius, functionStyleValue, - @"circleRadius should round-trip composite functions."); + @"Setting circleRadius to a camera-data expression should update circle-radius."); + XCTAssertEqualObjects(layer.circleRadius, functionExpression, + @"circleRadius should round-trip camera-data expressions."); layer.circleRadius = nil; XCTAssertTrue(rawLayer->getCircleRadius().isUndefined(), @"Unsetting circleRadius should return circle-radius to the default value."); - XCTAssertEqualObjects(layer.circleRadius, defaultStyleValue, + XCTAssertEqualObjects(layer.circleRadius, defaultExpression, @"circleRadius should return the default value after being unset."); // Transition property test layer.circleRadiusTransition = transitionTest; @@ -355,79 +376,88 @@ { XCTAssertTrue(rawLayer->getCirclePitchScale().isUndefined(), @"circle-pitch-scale should be unset initially."); - MGLStyleValue<NSValue *> *defaultStyleValue = layer.circleScaleAlignment; + NSExpression *defaultExpression = layer.circleScaleAlignment; - MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue:[NSValue valueWithMGLCircleScaleAlignment:MGLCircleScaleAlignmentViewport]]; - layer.circleScaleAlignment = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"'viewport'"]; + layer.circleScaleAlignment = constantExpression; mbgl::style::PropertyValue<mbgl::style::CirclePitchScaleType> propertyValue = { mbgl::style::CirclePitchScaleType::Viewport }; XCTAssertEqual(rawLayer->getCirclePitchScale(), propertyValue, - @"Setting circleScaleAlignment to a constant value should update circle-pitch-scale."); - XCTAssertEqualObjects(layer.circleScaleAlignment, constantStyleValue, - @"circleScaleAlignment should round-trip constant values."); - - MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.circleScaleAlignment = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::style::CirclePitchScaleType> intervalStops = { {{18, mbgl::style::CirclePitchScaleType::Viewport}} }; + @"Setting circleScaleAlignment to a constant value expression should update circle-pitch-scale."); + XCTAssertEqualObjects(layer.circleScaleAlignment, constantExpression, + @"circleScaleAlignment should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"'viewport'"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.circleScaleAlignment = functionExpression; + + mbgl::style::IntervalStops<mbgl::style::CirclePitchScaleType> intervalStops = {{ + { -INFINITY, mbgl::style::CirclePitchScaleType::Viewport }, + { 18, mbgl::style::CirclePitchScaleType::Viewport }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::style::CirclePitchScaleType> { intervalStops }; XCTAssertEqual(rawLayer->getCirclePitchScale(), propertyValue, - @"Setting circleScaleAlignment to a camera function should update circle-pitch-scale."); - XCTAssertEqualObjects(layer.circleScaleAlignment, functionStyleValue, - @"circleScaleAlignment should round-trip camera functions."); + @"Setting circleScaleAlignment to a camera expression should update circle-pitch-scale."); + XCTAssertEqualObjects(layer.circleScaleAlignment, functionExpression, + @"circleScaleAlignment should round-trip camera expressions."); layer.circleScaleAlignment = nil; XCTAssertTrue(rawLayer->getCirclePitchScale().isUndefined(), @"Unsetting circleScaleAlignment should return circle-pitch-scale to the default value."); - XCTAssertEqualObjects(layer.circleScaleAlignment, defaultStyleValue, + XCTAssertEqualObjects(layer.circleScaleAlignment, defaultExpression, @"circleScaleAlignment should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.circleScaleAlignment = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.circleScaleAlignment = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.circleScaleAlignment = functionExpression, NSException, NSInvalidArgumentException, @"MGLCircleLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.circleScaleAlignment = functionExpression, NSException, NSInvalidArgumentException, @"MGLCircleLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // circle-stroke-color { XCTAssertTrue(rawLayer->getCircleStrokeColor().isUndefined(), @"circle-stroke-color should be unset initially."); - MGLStyleValue<MGLColor *> *defaultStyleValue = layer.circleStrokeColor; + NSExpression *defaultExpression = layer.circleStrokeColor; - MGLStyleValue<MGLColor *> *constantStyleValue = [MGLStyleValue<MGLColor *> valueWithRawValue:[MGLColor redColor]]; - layer.circleStrokeColor = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + layer.circleStrokeColor = constantExpression; mbgl::style::DataDrivenPropertyValue<mbgl::Color> propertyValue = { { 1, 0, 0, 1 } }; XCTAssertEqual(rawLayer->getCircleStrokeColor(), propertyValue, - @"Setting circleStrokeColor to a constant value should update circle-stroke-color."); - XCTAssertEqualObjects(layer.circleStrokeColor, constantStyleValue, - @"circleStrokeColor should round-trip constant values."); - - MGLStyleValue<MGLColor *> * functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.circleStrokeColor = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::Color> intervalStops = { {{18, { 1, 0, 0, 1 }}} }; + @"Setting circleStrokeColor to a constant value expression should update circle-stroke-color."); + XCTAssertEqualObjects(layer.circleStrokeColor, constantExpression, + @"circleStrokeColor should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.circleStrokeColor = functionExpression; + + mbgl::style::IntervalStops<mbgl::Color> intervalStops = {{ + { -INFINITY, { 1, 0, 0, 1 } }, + { 18, { 1, 0, 0, 1 } }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::Color> { intervalStops }; XCTAssertEqual(rawLayer->getCircleStrokeColor(), propertyValue, - @"Setting circleStrokeColor to a camera function should update circle-stroke-color."); - XCTAssertEqualObjects(layer.circleStrokeColor, functionStyleValue, - @"circleStrokeColor should round-trip camera functions."); + @"Setting circleStrokeColor to a camera expression should update circle-stroke-color."); + XCTAssertEqualObjects(layer.circleStrokeColor, functionExpression, + @"circleStrokeColor should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.circleStrokeColor = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@18: constantExpression}]; + layer.circleStrokeColor = functionExpression; mbgl::style::ExponentialStops<mbgl::Color> exponentialStops = { {{18, { 1, 0, 0, 1 }}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<mbgl::Color> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getCircleStrokeColor(), propertyValue, - @"Setting circleStrokeColor to a source function should update circle-stroke-color."); - XCTAssertEqualObjects(layer.circleStrokeColor, functionStyleValue, - @"circleStrokeColor should round-trip source functions."); + @"Setting circleStrokeColor to a data expression should update circle-stroke-color."); + XCTAssertEqualObjects(layer.circleStrokeColor, functionExpression, + @"circleStrokeColor should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.circleStrokeColor = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + layer.circleStrokeColor = functionExpression; std::map<float, mbgl::Color> innerStops { {18, { 1, 0, 0, 1 }} }; mbgl::style::CompositeExponentialStops<mbgl::Color> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -435,15 +465,15 @@ propertyValue = mbgl::style::CompositeFunction<mbgl::Color> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getCircleStrokeColor(), propertyValue, - @"Setting circleStrokeColor to a composite function should update circle-stroke-color."); - XCTAssertEqualObjects(layer.circleStrokeColor, functionStyleValue, - @"circleStrokeColor should round-trip composite functions."); + @"Setting circleStrokeColor to a camera-data expression should update circle-stroke-color."); + XCTAssertEqualObjects(layer.circleStrokeColor, functionExpression, + @"circleStrokeColor should round-trip camera-data expressions."); layer.circleStrokeColor = nil; XCTAssertTrue(rawLayer->getCircleStrokeColor().isUndefined(), @"Unsetting circleStrokeColor should return circle-stroke-color to the default value."); - XCTAssertEqualObjects(layer.circleStrokeColor, defaultStyleValue, + XCTAssertEqualObjects(layer.circleStrokeColor, defaultExpression, @"circleStrokeColor should return the default value after being unset."); // Transition property test layer.circleStrokeColorTransition = transitionTest; @@ -460,40 +490,44 @@ { XCTAssertTrue(rawLayer->getCircleStrokeOpacity().isUndefined(), @"circle-stroke-opacity should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.circleStrokeOpacity; + NSExpression *defaultExpression = layer.circleStrokeOpacity; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.circleStrokeOpacity = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.circleStrokeOpacity = constantExpression; mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getCircleStrokeOpacity(), propertyValue, - @"Setting circleStrokeOpacity to a constant value should update circle-stroke-opacity."); - XCTAssertEqualObjects(layer.circleStrokeOpacity, constantStyleValue, - @"circleStrokeOpacity should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.circleStrokeOpacity = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting circleStrokeOpacity to a constant value expression should update circle-stroke-opacity."); + XCTAssertEqualObjects(layer.circleStrokeOpacity, constantExpression, + @"circleStrokeOpacity should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.circleStrokeOpacity = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getCircleStrokeOpacity(), propertyValue, - @"Setting circleStrokeOpacity to a camera function should update circle-stroke-opacity."); - XCTAssertEqualObjects(layer.circleStrokeOpacity, functionStyleValue, - @"circleStrokeOpacity should round-trip camera functions."); + @"Setting circleStrokeOpacity to a camera expression should update circle-stroke-opacity."); + XCTAssertEqualObjects(layer.circleStrokeOpacity, functionExpression, + @"circleStrokeOpacity should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.circleStrokeOpacity = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@18: constantExpression}]; + layer.circleStrokeOpacity = functionExpression; mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getCircleStrokeOpacity(), propertyValue, - @"Setting circleStrokeOpacity to a source function should update circle-stroke-opacity."); - XCTAssertEqualObjects(layer.circleStrokeOpacity, functionStyleValue, - @"circleStrokeOpacity should round-trip source functions."); + @"Setting circleStrokeOpacity to a data expression should update circle-stroke-opacity."); + XCTAssertEqualObjects(layer.circleStrokeOpacity, functionExpression, + @"circleStrokeOpacity should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.circleStrokeOpacity = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + layer.circleStrokeOpacity = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -501,15 +535,15 @@ propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getCircleStrokeOpacity(), propertyValue, - @"Setting circleStrokeOpacity to a composite function should update circle-stroke-opacity."); - XCTAssertEqualObjects(layer.circleStrokeOpacity, functionStyleValue, - @"circleStrokeOpacity should round-trip composite functions."); + @"Setting circleStrokeOpacity to a camera-data expression should update circle-stroke-opacity."); + XCTAssertEqualObjects(layer.circleStrokeOpacity, functionExpression, + @"circleStrokeOpacity should round-trip camera-data expressions."); layer.circleStrokeOpacity = nil; XCTAssertTrue(rawLayer->getCircleStrokeOpacity().isUndefined(), @"Unsetting circleStrokeOpacity should return circle-stroke-opacity to the default value."); - XCTAssertEqualObjects(layer.circleStrokeOpacity, defaultStyleValue, + XCTAssertEqualObjects(layer.circleStrokeOpacity, defaultExpression, @"circleStrokeOpacity should return the default value after being unset."); // Transition property test layer.circleStrokeOpacityTransition = transitionTest; @@ -526,40 +560,44 @@ { XCTAssertTrue(rawLayer->getCircleStrokeWidth().isUndefined(), @"circle-stroke-width should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.circleStrokeWidth; + NSExpression *defaultExpression = layer.circleStrokeWidth; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.circleStrokeWidth = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.circleStrokeWidth = constantExpression; mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getCircleStrokeWidth(), propertyValue, - @"Setting circleStrokeWidth to a constant value should update circle-stroke-width."); - XCTAssertEqualObjects(layer.circleStrokeWidth, constantStyleValue, - @"circleStrokeWidth should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.circleStrokeWidth = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting circleStrokeWidth to a constant value expression should update circle-stroke-width."); + XCTAssertEqualObjects(layer.circleStrokeWidth, constantExpression, + @"circleStrokeWidth should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.circleStrokeWidth = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getCircleStrokeWidth(), propertyValue, - @"Setting circleStrokeWidth to a camera function should update circle-stroke-width."); - XCTAssertEqualObjects(layer.circleStrokeWidth, functionStyleValue, - @"circleStrokeWidth should round-trip camera functions."); + @"Setting circleStrokeWidth to a camera expression should update circle-stroke-width."); + XCTAssertEqualObjects(layer.circleStrokeWidth, functionExpression, + @"circleStrokeWidth should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.circleStrokeWidth = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@18: constantExpression}]; + layer.circleStrokeWidth = functionExpression; mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getCircleStrokeWidth(), propertyValue, - @"Setting circleStrokeWidth to a source function should update circle-stroke-width."); - XCTAssertEqualObjects(layer.circleStrokeWidth, functionStyleValue, - @"circleStrokeWidth should round-trip source functions."); + @"Setting circleStrokeWidth to a data expression should update circle-stroke-width."); + XCTAssertEqualObjects(layer.circleStrokeWidth, functionExpression, + @"circleStrokeWidth should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.circleStrokeWidth = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + layer.circleStrokeWidth = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -567,15 +605,15 @@ propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getCircleStrokeWidth(), propertyValue, - @"Setting circleStrokeWidth to a composite function should update circle-stroke-width."); - XCTAssertEqualObjects(layer.circleStrokeWidth, functionStyleValue, - @"circleStrokeWidth should round-trip composite functions."); + @"Setting circleStrokeWidth to a camera-data expression should update circle-stroke-width."); + XCTAssertEqualObjects(layer.circleStrokeWidth, functionExpression, + @"circleStrokeWidth should round-trip camera-data expressions."); layer.circleStrokeWidth = nil; XCTAssertTrue(rawLayer->getCircleStrokeWidth().isUndefined(), @"Unsetting circleStrokeWidth should return circle-stroke-width to the default value."); - XCTAssertEqualObjects(layer.circleStrokeWidth, defaultStyleValue, + XCTAssertEqualObjects(layer.circleStrokeWidth, defaultExpression, @"circleStrokeWidth should return the default value after being unset."); // Transition property test layer.circleStrokeWidthTransition = transitionTest; @@ -592,84 +630,94 @@ { XCTAssertTrue(rawLayer->getCircleTranslate().isUndefined(), @"circle-translate should be unset initially."); - MGLStyleValue<NSValue *> *defaultStyleValue = layer.circleTranslation; + NSExpression *defaultExpression = layer.circleTranslation; - MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue: + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", #if TARGET_OS_IPHONE [NSValue valueWithCGVector:CGVectorMake(1, 1)] #else [NSValue valueWithMGLVector:CGVectorMake(1, -1)] #endif ]; - layer.circleTranslation = constantStyleValue; + layer.circleTranslation = constantExpression; mbgl::style::PropertyValue<std::array<float, 2>> propertyValue = { { 1, 1 } }; XCTAssertEqual(rawLayer->getCircleTranslate(), propertyValue, - @"Setting circleTranslation to a constant value should update circle-translate."); - XCTAssertEqualObjects(layer.circleTranslation, constantStyleValue, - @"circleTranslation should round-trip constant values."); - - MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.circleTranslation = functionStyleValue; - - mbgl::style::IntervalStops<std::array<float, 2>> intervalStops = { {{18, { 1, 1 }}} }; + @"Setting circleTranslation to a constant value expression should update circle-translate."); + XCTAssertEqualObjects(layer.circleTranslation, constantExpression, + @"circleTranslation should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"{1, 1}"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.circleTranslation = functionExpression; + + mbgl::style::IntervalStops<std::array<float, 2>> intervalStops = {{ + { -INFINITY, { 1, 1 } }, + { 18, { 1, 1 } }, + }}; propertyValue = mbgl::style::CameraFunction<std::array<float, 2>> { intervalStops }; XCTAssertEqual(rawLayer->getCircleTranslate(), propertyValue, - @"Setting circleTranslation to a camera function should update circle-translate."); - XCTAssertEqualObjects(layer.circleTranslation, functionStyleValue, - @"circleTranslation should round-trip camera functions."); + @"Setting circleTranslation to a camera expression should update circle-translate."); + XCTAssertEqualObjects(layer.circleTranslation, functionExpression, + @"circleTranslation should round-trip camera expressions."); layer.circleTranslation = nil; XCTAssertTrue(rawLayer->getCircleTranslate().isUndefined(), @"Unsetting circleTranslation should return circle-translate to the default value."); - XCTAssertEqualObjects(layer.circleTranslation, defaultStyleValue, + XCTAssertEqualObjects(layer.circleTranslation, defaultExpression, @"circleTranslation should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.circleTranslation = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.circleTranslation = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.circleTranslation = functionExpression, NSException, NSInvalidArgumentException, @"MGLCircleLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.circleTranslation = functionExpression, NSException, NSInvalidArgumentException, @"MGLCircleLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // circle-translate-anchor { XCTAssertTrue(rawLayer->getCircleTranslateAnchor().isUndefined(), @"circle-translate-anchor should be unset initially."); - MGLStyleValue<NSValue *> *defaultStyleValue = layer.circleTranslationAnchor; + NSExpression *defaultExpression = layer.circleTranslationAnchor; - MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue:[NSValue valueWithMGLCircleTranslationAnchor:MGLCircleTranslationAnchorViewport]]; - layer.circleTranslationAnchor = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"'viewport'"]; + layer.circleTranslationAnchor = constantExpression; mbgl::style::PropertyValue<mbgl::style::TranslateAnchorType> propertyValue = { mbgl::style::TranslateAnchorType::Viewport }; XCTAssertEqual(rawLayer->getCircleTranslateAnchor(), propertyValue, - @"Setting circleTranslationAnchor to a constant value should update circle-translate-anchor."); - XCTAssertEqualObjects(layer.circleTranslationAnchor, constantStyleValue, - @"circleTranslationAnchor should round-trip constant values."); - - MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.circleTranslationAnchor = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::style::TranslateAnchorType> intervalStops = { {{18, mbgl::style::TranslateAnchorType::Viewport}} }; + @"Setting circleTranslationAnchor to a constant value expression should update circle-translate-anchor."); + XCTAssertEqualObjects(layer.circleTranslationAnchor, constantExpression, + @"circleTranslationAnchor should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"'viewport'"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.circleTranslationAnchor = functionExpression; + + mbgl::style::IntervalStops<mbgl::style::TranslateAnchorType> intervalStops = {{ + { -INFINITY, mbgl::style::TranslateAnchorType::Viewport }, + { 18, mbgl::style::TranslateAnchorType::Viewport }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::style::TranslateAnchorType> { intervalStops }; XCTAssertEqual(rawLayer->getCircleTranslateAnchor(), propertyValue, - @"Setting circleTranslationAnchor to a camera function should update circle-translate-anchor."); - XCTAssertEqualObjects(layer.circleTranslationAnchor, functionStyleValue, - @"circleTranslationAnchor should round-trip camera functions."); + @"Setting circleTranslationAnchor to a camera expression should update circle-translate-anchor."); + XCTAssertEqualObjects(layer.circleTranslationAnchor, functionExpression, + @"circleTranslationAnchor should round-trip camera expressions."); layer.circleTranslationAnchor = nil; XCTAssertTrue(rawLayer->getCircleTranslateAnchor().isUndefined(), @"Unsetting circleTranslationAnchor should return circle-translate-anchor to the default value."); - XCTAssertEqualObjects(layer.circleTranslationAnchor, defaultStyleValue, + XCTAssertEqualObjects(layer.circleTranslationAnchor, defaultExpression, @"circleTranslationAnchor should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.circleTranslationAnchor = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.circleTranslationAnchor = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.circleTranslationAnchor = functionExpression, NSException, NSInvalidArgumentException, @"MGLCircleLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.circleTranslationAnchor = functionExpression, NSException, NSInvalidArgumentException, @"MGLCircleLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } } diff --git a/platform/darwin/test/MGLDocumentationExampleTests.swift b/platform/darwin/test/MGLDocumentationExampleTests.swift index 668e5f57f8..d3e2bf91f2 100644 --- a/platform/darwin/test/MGLDocumentationExampleTests.swift +++ b/platform/darwin/test/MGLDocumentationExampleTests.swift @@ -137,12 +137,15 @@ class MGLDocumentationExampleTests: XCTestCase, MGLMapViewDelegate { //#-example-code let layer = MGLCircleStyleLayer(identifier: "circles", source: population) layer.sourceLayerIdentifier = "population" - layer.circleColor = MGLStyleValue(rawValue: .green) - layer.circleRadius = MGLStyleValue(interpolationMode: .exponential, - cameraStops: [12: MGLStyleValue(rawValue: 2), - 22: MGLStyleValue(rawValue: 180)], - options: [.interpolationBase: 1.75]) - layer.circleOpacity = MGLStyleValue(rawValue: 0.7) + #if os(macOS) + layer.circleColor = NSExpression(forConstantValue: NSColor.green) + #else + layer.circleColor = NSExpression(forConstantValue: UIColor.green) + #endif + layer.circleRadius = NSExpression(format: "FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'exponential', 1.75, %@)", + [12: 2, + 22: 180]) + layer.circleOpacity = NSExpression(forConstantValue: 0.7) layer.predicate = NSPredicate(format: "%K == %@", "marital-status", "married") mapView.style?.addLayer(layer) //#-end-example-code @@ -157,12 +160,15 @@ class MGLDocumentationExampleTests: XCTestCase, MGLMapViewDelegate { //#-example-code let layer = MGLLineStyleLayer(identifier: "trails-path", source: trails) layer.sourceLayerIdentifier = "trails" - layer.lineWidth = MGLStyleValue(interpolationMode: .exponential, - cameraStops: [14: MGLStyleValue(rawValue: 2), - 18: MGLStyleValue(rawValue: 20)], - options: [.interpolationBase: 1.5]) - layer.lineColor = MGLStyleValue(rawValue: .brown) - layer.lineCap = MGLStyleValue(rawValue: NSValue(mglLineCap: .round)) + layer.lineWidth = NSExpression(format: "FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'exponential', 1.5, %@)", + [14: 2, + 18: 20]) + #if os(macOS) + layer.lineColor = NSExpression(forConstantValue: NSColor.brown) + #else + layer.lineColor = NSExpression(forConstantValue: UIColor.brown) + #endif + layer.lineCap = NSExpression(forConstantValue: "round") layer.predicate = NSPredicate(format: "%K == %@", "trail-type", "mountain-biking") mapView.style?.addLayer(layer) //#-end-example-code @@ -177,7 +183,11 @@ class MGLDocumentationExampleTests: XCTestCase, MGLMapViewDelegate { //#-example-code let layer = MGLFillStyleLayer(identifier: "parks", source: parks) layer.sourceLayerIdentifier = "parks" - layer.fillColor = MGLStyleValue(rawValue: .green) + #if os(macOS) + layer.fillColor = NSExpression(forConstantValue: NSColor.green) + #else + layer.fillColor = NSExpression(forConstantValue: UIColor.green) + #endif layer.predicate = NSPredicate(format: "type == %@", "national-park") mapView.style?.addLayer(layer) //#-end-example-code @@ -192,8 +202,8 @@ class MGLDocumentationExampleTests: XCTestCase, MGLMapViewDelegate { //#-example-code let layer = MGLFillExtrusionStyleLayer(identifier: "buildings", source: buildings) layer.sourceLayerIdentifier = "building" - layer.fillExtrusionHeight = MGLStyleValue(interpolationMode: .identity, sourceStops: nil, attributeName: "height", options: nil) - layer.fillExtrusionBase = MGLStyleValue(interpolationMode: .identity, sourceStops: nil, attributeName: "min_height", options: nil) + layer.fillExtrusionHeight = NSExpression(forKeyPath: "height") + layer.fillExtrusionBase = NSExpression(forKeyPath: "min_height") layer.predicate = NSPredicate(format: "extrude == 'true'") mapView.style?.addLayer(layer) //#-end-example-code @@ -208,17 +218,17 @@ class MGLDocumentationExampleTests: XCTestCase, MGLMapViewDelegate { //#-example-code let layer = MGLSymbolStyleLayer(identifier: "coffeeshops", source: pois) layer.sourceLayerIdentifier = "pois" - layer.iconImageName = MGLStyleValue(rawValue: "coffee") - layer.iconScale = MGLStyleValue(rawValue: 0.5) - layer.text = MGLStyleValue(rawValue: "{name}") + layer.iconImageName = NSExpression(forConstantValue: "coffee") + layer.iconScale = NSExpression(forConstantValue: 0.5) + layer.text = NSExpression(forKeyPath: "name") #if os(macOS) var vector = CGVector(dx: 10, dy: 0) - layer.textTranslation = MGLStyleValue(rawValue: NSValue(bytes: &vector, objCType: "{CGVector=dd}")) + layer.textTranslation = NSExpression(forConstantValue: NSValue(bytes: &vector, objCType: "{CGVector=dd}")) #else - layer.textTranslation = MGLStyleValue(rawValue: NSValue(cgVector: CGVector(dx: 10, dy: 0))) + layer.textTranslation = NSExpression(forConstantValue: NSValue(cgVector: CGVector(dx: 10, dy: 0))) #endif - layer.textJustification = MGLStyleValue(rawValue: NSValue(mglTextJustification: .left)) - layer.textAnchor = MGLStyleValue(rawValue: NSValue(mglTextAnchor: .left)) + layer.textJustification = NSExpression(forConstantValue: "left") + layer.textAnchor = NSExpression(forConstantValue: "left") layer.predicate = NSPredicate(format: "%K == %@", "venue-type", "coffee") mapView.style?.addLayer(layer) //#-end-example-code @@ -239,7 +249,7 @@ class MGLDocumentationExampleTests: XCTestCase, MGLMapViewDelegate { //#-example-code let layer = MGLRasterStyleLayer(identifier: "clouds", source: source) - layer.rasterOpacity = MGLStyleValue(rawValue: 0.5) + layer.rasterOpacity = NSExpression(forConstantValue: 0.5) mapView.style?.addLayer(layer) //#-end-example-code diff --git a/platform/darwin/test/MGLDocumentationGuideTests.swift b/platform/darwin/test/MGLDocumentationGuideTests.swift index c71f1b46c7..f939695f32 100644 --- a/platform/darwin/test/MGLDocumentationGuideTests.swift +++ b/platform/darwin/test/MGLDocumentationGuideTests.swift @@ -1,3 +1,4 @@ +import XCTest import Foundation import Mapbox #if os(iOS) @@ -52,25 +53,26 @@ class MGLDocumentationGuideTests: XCTestCase, MGLMapViewDelegate { func testUsingStyleFunctionsAtRuntime$Stops() { //#-example-code #if os(macOS) - let stops = [ - 0: MGLStyleValue<NSColor>(rawValue: .yellow), - 2.5: MGLStyleValue(rawValue: .orange), - 5: MGLStyleValue(rawValue: .red), - 7.5: MGLStyleValue(rawValue: .blue), - 10: MGLStyleValue(rawValue: .white), + let stops: [Float: NSColor] = [ + 0: .yellow, + 2.5: .orange, + 5: .red, + 7.5: .blue, + 10: .white, ] #else - let stops = [ - 0: MGLStyleValue<UIColor>(rawValue: .yellow), - 2.5: MGLStyleValue(rawValue: .orange), - 5: MGLStyleValue(rawValue: .red), - 7.5: MGLStyleValue(rawValue: .blue), - 10: MGLStyleValue(rawValue: .white), + let stops: [Float: UIColor] = [ + 0: .yellow, + 2.5: .orange, + 5: .red, + 7.5: .blue, + 10: .white, ] #endif //#-end-example-code - let _ = MGLStyleValue(interpolationMode: .exponential, cameraStops: stops, options: nil) + let _ = NSExpression(format: "FUNCTION(mag, 'mgl_stepWithMinimum:stops:', %@, %@)", + stops[0]!, stops) } func testUsingStyleFunctionsAtRuntime$Linear() { @@ -83,36 +85,32 @@ class MGLDocumentationGuideTests: XCTestCase, MGLMapViewDelegate { mapView.style?.addSource(source) #if os(macOS) - let stops = [ - 0: MGLStyleValue<NSColor>(rawValue: .yellow), - 2.5: MGLStyleValue(rawValue: .orange), - 5: MGLStyleValue(rawValue: .red), - 7.5: MGLStyleValue(rawValue: .blue), - 10: MGLStyleValue(rawValue: .white), + let stops: [Float: NSColor] = [ + 0: .yellow, + 2.5: .orange, + 5: .red, + 7.5: .blue, + 10: .white, ] #else - let stops = [ - 0: MGLStyleValue<UIColor>(rawValue: .yellow), - 2.5: MGLStyleValue(rawValue: .orange), - 5: MGLStyleValue(rawValue: .red), - 7.5: MGLStyleValue(rawValue: .blue), - 10: MGLStyleValue(rawValue: .white), + let stops: [Float: UIColor] = [ + 0: .yellow, + 2.5: .orange, + 5: .red, + 7.5: .blue, + 10: .white, ] #endif let layer = MGLCircleStyleLayer(identifier: "circles", source: source) #if os(macOS) - layer.circleColor = MGLStyleValue(interpolationMode: .exponential, - sourceStops: stops, - attributeName: "mag", - options: [.defaultValue: MGLStyleValue<NSColor>(rawValue: .green)]) + layer.circleColor = NSExpression(format: "FUNCTION(mag, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", + stops) #else - layer.circleColor = MGLStyleValue(interpolationMode: .exponential, - sourceStops: stops, - attributeName: "mag", - options: [.defaultValue: MGLStyleValue<UIColor>(rawValue: .green)]) + layer.circleColor = NSExpression(format: "FUNCTION(mag, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", + stops) #endif - layer.circleRadius = MGLStyleValue(rawValue: 10) + layer.circleRadius = NSExpression(forConstantValue: 10) mapView.style?.insertLayer(layer, below: symbolLayer) //#-end-example-code } @@ -123,14 +121,13 @@ class MGLDocumentationGuideTests: XCTestCase, MGLMapViewDelegate { //#-example-code let stops = [ - 12: MGLStyleValue<NSNumber>(rawValue: 0.5), - 14: MGLStyleValue(rawValue: 2), - 18: MGLStyleValue(rawValue: 18), + 12: 0.5, + 14: 2, + 18: 18, ] - layer.circleRadius = MGLStyleValue(interpolationMode: .exponential, - cameraStops: stops, - options: [.interpolationBase: 1.5]) + layer.circleRadius = NSExpression(format: "FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'exponential', 1.5, %@)", + stops) //#-end-example-code } @@ -140,31 +137,27 @@ class MGLDocumentationGuideTests: XCTestCase, MGLMapViewDelegate { //#-example-code #if os(macOS) - let stops = [ - 0: MGLStyleValue<NSColor>(rawValue: .yellow), - 2.5: MGLStyleValue(rawValue: .orange), - 5: MGLStyleValue(rawValue: .red), - 7.5: MGLStyleValue(rawValue: .blue), - 10: MGLStyleValue(rawValue: .white), + let stops: [Float: NSColor] = [ + 0: .yellow, + 2.5: .orange, + 5: .red, + 7.5: .blue, + 10: .white, ] - layer.circleColor = MGLStyleValue(interpolationMode: .interval, - sourceStops: stops, - attributeName: "mag", - options: [.defaultValue: MGLStyleValue<NSColor>(rawValue: .green)]) + layer.circleColor = NSExpression(format: "FUNCTION(mag, 'mgl_stepWithMinimum:stops:', %@, %@)", + NSColor.green, stops) #else - let stops = [ - 0: MGLStyleValue<UIColor>(rawValue: .yellow), - 2.5: MGLStyleValue(rawValue: .orange), - 5: MGLStyleValue(rawValue: .red), - 7.5: MGLStyleValue(rawValue: .blue), - 10: MGLStyleValue(rawValue: .white), + let stops: [Float: UIColor] = [ + 0: .yellow, + 2.5: .orange, + 5: .red, + 7.5: .blue, + 10: .white, ] - layer.circleColor = MGLStyleValue(interpolationMode: .interval, - sourceStops: stops, - attributeName: "mag", - options: [.defaultValue: MGLStyleValue<UIColor>(rawValue: .green)]) + layer.circleColor = NSExpression(format: "FUNCTION(mag, 'mgl_stepWithMinimum:stops:', %@, %@)", + UIColor.green, stops) #endif //#-end-example-code } @@ -175,28 +168,24 @@ class MGLDocumentationGuideTests: XCTestCase, MGLMapViewDelegate { //#-example-code #if os(macOS) - let categoricalStops = [ - "earthquake": MGLStyleValue<NSColor>(rawValue: .orange), - "explosion": MGLStyleValue(rawValue: .red), - "quarry blast": MGLStyleValue(rawValue: .yellow), + let colors: [String: NSColor] = [ + "earthquake": .orange, + "explosion": .red, + "quarry blast": .yellow, ] - - layer.circleColor = MGLStyleValue(interpolationMode: .categorical, - sourceStops: categoricalStops, - attributeName: "type", - options: [.defaultValue: MGLStyleValue<NSColor>(rawValue: .blue)]) + let defaultColor = NSColor.blue #else - let categoricalStops = [ - "earthquake": MGLStyleValue<UIColor>(rawValue: .orange), - "explosion": MGLStyleValue(rawValue: .red), - "quarry blast": MGLStyleValue(rawValue: .yellow), + let colors: [String: UIColor] = [ + "earthquake": .orange, + "explosion": .red, + "quarry blast": .yellow, ] - - layer.circleColor = MGLStyleValue(interpolationMode: .categorical, - sourceStops: categoricalStops, - attributeName: "type", - options: [.defaultValue: MGLStyleValue<UIColor>(rawValue: .blue)]) + let defaultColor = UIColor.blue #endif + + layer.circleColor = NSExpression( + format: "TERNARY(FUNCTION(%@, 'valueForKeyPath:', type) != nil, FUNCTION(%@, 'valueForKeyPath:', type), %@)", + colors, colors, defaultColor) //#-end-example-code } @@ -205,10 +194,7 @@ class MGLDocumentationGuideTests: XCTestCase, MGLMapViewDelegate { let layer = MGLCircleStyleLayer(identifier: "circles", source: source) //#-example-code - layer.circleRadius = MGLStyleValue(interpolationMode: .identity, - sourceStops: nil, - attributeName: "mag", - options: [.defaultValue: MGLStyleValue<NSNumber>(rawValue: 0)]) + layer.circleRadius = NSExpression(forKeyPath: "mag") //#-end-example-code } } diff --git a/platform/darwin/test/MGLExpressionTests.mm b/platform/darwin/test/MGLExpressionTests.mm index ad0833a068..821c5dbdb4 100644 --- a/platform/darwin/test/MGLExpressionTests.mm +++ b/platform/darwin/test/MGLExpressionTests.mm @@ -1,9 +1,17 @@ #import <XCTest/XCTest.h> +#import "MGLStyleLayerTests.h" + #import <string> #import "MGLTypes.h" -#import "NSExpression+MGLAdditions.h" +#import "NSExpression+MGLPrivateAdditions.h" +#import "NSValue+MGLAdditions.h" +#if TARGET_OS_IPHONE +#import "UIColor+MGLAdditions.h" +#else +#import "NSColor+MGLAdditions.h" +#endif #define MGLAssertEqualValues(actual, expected, ...) \ XCTAssertTrue(actual.is<__typeof__(expected)>()); \ @@ -17,11 +25,14 @@ XCTAssertEqualWithAccuracy(actual.get<__typeof__(expected)>(), expected, accuracy, __VA_ARGS__); \ } +#define MGLConstantExpression(constant) \ + [NSExpression expressionForConstantValue:constant] + #define MGLAssertConstantEqualsValue(constant, value, ...) \ - MGLAssertEqualValues([NSExpression expressionForConstantValue:constant].mgl_constantMBGLValue, value, __VA_ARGS__); + MGLAssertEqualValues(MGLConstantExpression(constant).mgl_constantMBGLValue, value, __VA_ARGS__); #define MGLAssertConstantEqualsValueWithAccuracy(constant, value, accuracy, ...) \ - MGLAssertEqualValuesWithAccuracy([NSExpression expressionForConstantValue:constant].mgl_constantMBGLValue, value, accuracy, __VA_ARGS__); + MGLAssertEqualValuesWithAccuracy(MGLConstantExpression(constant).mgl_constantMBGLValue, value, accuracy, __VA_ARGS__); using namespace std::string_literals; @@ -140,4 +151,446 @@ using namespace std::string_literals; XCTAssertEqual([NSExpression expressionForConstantValue:nil].mgl_featureType, mbgl::FeatureType::Unknown); } +#pragma mark - JSON expression object tests + +- (void)testVariableExpressionObject { + { + NSExpression *expression = [NSExpression expressionForVariable:@"zoomLevel"]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, @[@"zoom"]); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"$zoomLevel"].mgl_jsonExpressionObject, @[@"zoom"]); + XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:@[@"zoom"]], expression); + NSMutableDictionary *context = [@{@"zoomLevel": @16} mutableCopy]; + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:context], @16); + } + { + NSExpression *expression = [NSExpression expressionForVariable:@"heatmapDensity"]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, @[@"heatmap-density"]); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"$heatmapDensity"].mgl_jsonExpressionObject, @[@"heatmap-density"]); + XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:@[@"heatmap-density"]], expression); + NSMutableDictionary *context = [@{@"heatmapDensity": @1} mutableCopy]; + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:context], @1); + } + { + NSExpression *expression = [NSExpression expressionForVariable:@"loremIpsum"]; + NSArray *jsonExpression = @[@"var", @"loremIpsum"]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"$loremIpsum"].mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression); + NSMutableDictionary *context = [@{@"loremIpsum": @"Lorem ipsum dolor sit amet"} mutableCopy]; + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:context], @"Lorem ipsum dolor sit amet"); + } + { + NSDictionary *context = @{@"loremIpsum": MGLConstantExpression(@"Lorem ipsum dolor sit amet")}; + NSExpression *expression = [NSExpression expressionWithFormat:@"FUNCTION(uppercase($loremIpsum), 'mgl_expressionWithContext:', %@)", context]; + NSArray *jsonExpression = @[@"let", @"loremIpsum", @"Lorem ipsum dolor sit amet", @[@"upcase", @[@"var", @"loremIpsum"]]]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression); + } +} + +- (void)testConstantValueExpressionObject { + { + NSExpression *expression = [NSExpression expressionForConstantValue:nil]; + XCTAssert(expression.mgl_jsonExpressionObject == [NSNull null]); + XCTAssert([NSExpression expressionWithFormat:@"nil"].mgl_jsonExpressionObject == [NSNull null]); + XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:[NSNull null]], expression); + XCTAssertNil([expression expressionValueWithObject:nil context:nil]); + } + { + NSExpression *expression = [NSExpression expressionForConstantValue:@1]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, @1); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"1"].mgl_jsonExpressionObject, @1); + XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:@1], expression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @1); + } + { + NSExpression *expression = [NSExpression expressionForConstantValue:@YES]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, @YES); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"TRUE"].mgl_jsonExpressionObject, @YES); + XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:@YES], expression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @YES); + } + { + NSExpression *expression = [NSExpression expressionForConstantValue:nil]; + XCTAssert(expression.mgl_jsonExpressionObject == [NSNull null]); + XCTAssert([NSExpression expressionWithFormat:@"nil"].mgl_jsonExpressionObject == [NSNull null]); + XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:[NSNull null]], expression); + XCTAssertNil([expression expressionValueWithObject:nil context:nil]); + } + { + CGVector vector = CGVectorMake(1, 2); + NSExpression *expression = [NSExpression expressionForConstantValue:@(vector)]; +#if !TARGET_OS_IPHONE + NSArray *jsonExpression = @[@"literal", @[@1, @-2]]; +#else + NSArray *jsonExpression = @[@"literal", @[@1, @2]]; +#endif + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + // No way to distinguish offsets from ordinary arrays in expressions. + XCTAssertEqualObjects([[NSExpression mgl_expressionWithJSONObject:jsonExpression].collection valueForKeyPath:@"constantValue"], jsonExpression.lastObject); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @(vector)); + } + { +#if !TARGET_OS_IPHONE + NSEdgeInsets padding = {1, 2, 3, 4}; + NSValue *value = [NSValue valueWithEdgeInsets:padding]; +#else + UIEdgeInsets padding = {1, 2, 3, 4}; + NSValue *value = [NSValue valueWithUIEdgeInsets:padding]; +#endif + NSExpression *expression = [NSExpression expressionForConstantValue:value]; + NSArray *jsonExpression = @[@"literal", @[@1, @4, @3, @2]]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + // No way to distinguish offsets from ordinary arrays in expressions. + XCTAssertEqualObjects([[NSExpression mgl_expressionWithJSONObject:jsonExpression].collection valueForKeyPath:@"constantValue"], jsonExpression.lastObject); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], value); + } + { + MGLColor *color = [MGLColor mgl_colorWithColor:{ 255.0/255, 239.0/255, 213.0/255, 1 }]; // papayawhip + NSExpression *expression = [NSExpression expressionForConstantValue:color]; + NSArray *jsonExpression = @[@"rgb", @255, @239, @213]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], color); + } + { + MGLColor *color = [MGLColor mgl_colorWithColor:{ 255.0/255, 239.0/255, 213.0/255, 0.5 }]; // papayawhip + NSExpression *expression = [NSExpression expressionForConstantValue:color]; + NSArray *jsonExpression = @[@"rgba", @255, @239, @213, @0.5]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], color); + } + { + NSExpression *expression = [NSExpression expressionWithFormat:@"noindex(513)"]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, @513); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @513); + } +} + +- (void)testKeyPathExpressionObject { + { + NSExpression *expression = [NSExpression expressionForKeyPath:@"highway"]; + NSArray *jsonExpression = @[@"get", @"highway"]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"highway"].mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionWithFormat:@"%@.population", @{@"population": MGLConstantExpression(@12000)}]; + NSArray *jsonExpression = @[@"get", @"population", @[@"literal", @{@"population": @12000}]]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionWithFormat:@"%@.uppercase('population')", @{@"POPULATION": MGLConstantExpression(@12000)}]; + NSArray *jsonExpression = @[@"get", @[@"upcase", @"population"], @[@"literal", @{@"POPULATION": @12000}]]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression); + } +} + +- (void)testStatisticalExpressionObject { + { + NSExpression *expression = [NSExpression expressionWithFormat:@"average({1, 2, 2, 3, 4, 7, 9})"]; + NSArray *jsonExpression = @[@"/", @[@"+", @1, @2, @2, @3, @4, @7, @9], @[@"length", @[@"literal", @[@1, @2, @2, @3, @4, @7, @9]]]]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @4); + } + { + NSExpression *expression = [NSExpression expressionWithFormat:@"sum({1, 2, 2, 3, 4, 7, 9})"]; + NSArray *jsonExpression = @[@"+", @1, @2, @2, @3, @4, @7, @9]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @28); + XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionWithFormat:@"count({1, 2, 2, 3, 4, 7, 9})"]; + NSArray *jsonExpression = @[@"length", @[@"literal", @[@1, @2, @2, @3, @4, @7, @9]]]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @7); + XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionWithFormat:@"min({1, 2, 2, 3, 4, 7, 9})"]; + NSArray *jsonExpression = @[@"min", @1, @2, @2, @3, @4, @7, @9]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @1); + XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionWithFormat:@"max({1, 2, 2, 3, 4, 7, 9})"]; + NSArray *jsonExpression = @[@"max", @1, @2, @2, @3, @4, @7, @9]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @9); + XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression); + } +} + +- (void)testArithmeticExpressionObject { + NSArray *arguments = @[MGLConstantExpression(@1), MGLConstantExpression(@1)]; + { + NSExpression *expression = [NSExpression expressionForFunction:@"add:to:" arguments:arguments]; + NSArray *jsonExpression = @[@"+", @1, @1]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"1 + 1"].mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"from:subtract:" arguments:arguments]; + NSArray *jsonExpression = @[@"-", @1, @1]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"1 - 1"].mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"multiply:by:" arguments:arguments]; + NSArray *jsonExpression = @[@"*", @1, @1]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"1 * 1"].mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"divide:by:" arguments:arguments]; + NSArray *jsonExpression = @[@"/", @1, @1]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"1 / 1"].mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"modulus:by:" arguments:arguments]; + NSArray *jsonExpression = @[@"%", @1, @1]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + // NSExpression lacks a shorthand operator for modulus. + XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"ceiling:" arguments:@[MGLConstantExpression(@1.5)]]; + NSArray *jsonTruncation = @[@"-", @1.5, @[@"%", @1.5, @1]]; + NSArray *jsonExpression = @[@"+", jsonTruncation, @[@"case", @[@">", @[@"%", @1.5, @1], @0], @1, @0]]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @2); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"ceiling:" arguments:@[MGLConstantExpression(@-1.5)]]; + NSArray *jsonTruncation = @[@"-", @-1.5, @[@"%", @-1.5, @1]]; + NSArray *jsonExpression = @[@"+", jsonTruncation, @[@"case", @[@">", @[@"%", @-1.5, @1], @0], @1, @0]]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @-1); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"ceiling:" arguments:@[MGLConstantExpression(@2)]]; + NSArray *jsonTruncation = @[@"-", @2, @[@"%", @2, @1]]; + NSArray *jsonExpression = @[@"+", jsonTruncation, @[@"case", @[@">", @[@"%", @2, @1], @0], @1, @0]]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @2); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"ceiling:" arguments:@[MGLConstantExpression(@-2)]]; + NSArray *jsonTruncation = @[@"-", @-2, @[@"%", @-2, @1]]; + NSArray *jsonExpression = @[@"+", jsonTruncation, @[@"case", @[@">", @[@"%", @-2, @1], @0], @1, @0]]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @-2); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"trunc:" arguments:@[MGLConstantExpression(@1.5)]]; + NSArray *jsonExpression = @[@"-", @1.5, @[@"%", @1.5, @1]]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @1); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"trunc:" arguments:@[MGLConstantExpression(@-1.5)]]; + NSArray *jsonExpression = @[@"-", @-1.5, @[@"%", @-1.5, @1]]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @-1); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"abs:" arguments:@[MGLConstantExpression(@2)]]; + NSArray *jsonExpression = @[@"*", @2, @[@"case", @[@">", @2, @0], @1, @-1]]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @2); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"abs:" arguments:@[MGLConstantExpression(@-2)]]; + NSArray *jsonExpression = @[@"*", @-2, @[@"case", @[@">", @-2, @0], @1, @-1]]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @2); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"floor:" arguments:@[MGLConstantExpression(@1.5)]]; + NSArray *jsonTruncation = @[@"-", @1.5, @[@"%", @1.5, @1]]; + NSArray *jsonExpression = @[@"-", jsonTruncation, @[@"case", @[@"<", @[@"%", @1.5, @1], @0], @1, @0]]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @1); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"floor:" arguments:@[MGLConstantExpression(@-1.5)]]; + NSArray *jsonTruncation = @[@"-", @-1.5, @[@"%", @-1.5, @1]]; + NSArray *jsonExpression = @[@"-", jsonTruncation, @[@"case", @[@"<", @[@"%", @-1.5, @1], @0], @1, @0]]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @-2); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"floor:" arguments:@[MGLConstantExpression(@2)]]; + NSArray *jsonTruncation = @[@"-", @2, @[@"%", @2, @1]]; + NSArray *jsonExpression = @[@"-", jsonTruncation, @[@"case", @[@"<", @[@"%", @2, @1], @0], @1, @0]]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @2); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"floor:" arguments:@[MGLConstantExpression(@-2)]]; + NSArray *jsonTruncation = @[@"-", @-2, @[@"%", @-2, @1]]; + NSArray *jsonExpression = @[@"-", jsonTruncation, @[@"case", @[@"<", @[@"%", @-2, @1], @0], @1, @0]]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @-2); + } +} + +- (void)testTrigonometricExpressionObject { + NSArray *arguments = @[MGLConstantExpression(@1), MGLConstantExpression(@1)]; + { + NSExpression *expression = [NSExpression expressionForFunction:@"sqrt:" arguments:arguments]; + NSArray *jsonExpression = @[@"sqrt", @1, @1]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"ln:" arguments:arguments]; + NSArray *jsonExpression = @[@"ln", @1, @1]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"raise:toPower:" arguments:arguments]; + NSArray *jsonExpression = @[@"^", @1, @1]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"1 ** 1"].mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"exp:" arguments:@[MGLConstantExpression(@0)]]; + NSArray *jsonExpression = @[@"^", @[@"e"], @0]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @1); + XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForConstantValue:@(M_E)]; + NSArray *jsonExpression = @[@"e"]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @(M_E)); + XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForConstantValue:@(M_PI)]; + NSArray *jsonExpression = @[@"pi"]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @(M_PI)); + XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression); + } +} + +- (void)testStringFormattingExpressionObject { + NSArray *arguments = @[MGLConstantExpression(@"MacDonald")]; + { + NSExpression *expression = [NSExpression expressionWithFormat:@"FUNCTION('Old', 'stringByAppendingString:', 'MacDonald')"]; + NSArray *jsonExpression = @[@"concat", @"Old", @"MacDonald"]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @"OldMacDonald"); + XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"uppercase:" arguments:arguments]; + NSArray *jsonExpression = @[@"upcase", @"MacDonald"]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"lowercase:" arguments:arguments]; + NSArray *jsonExpression = @[@"downcase", @"MacDonald"]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression); + } +} + +- (void)testTypeConversionExpressionObject { + { + NSExpression *expression = [NSExpression expressionWithFormat:@"FUNCTION(number, 'boolValue')"]; + NSArray *jsonExpression = @[@"to-boolean", @[@"get", @"number"]]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + // NSExpression is unable to evaluate -[NSNumber boolValue] by itself + // because it returns a primitive instead of an object. + XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionWithFormat:@"FUNCTION(postalCode, 'mgl_numberWithFallbackValues:', zipCode)"]; + NSArray *jsonExpression = @[@"to-number", @[@"get", @"postalCode"], @[@"get", @"zipCode"]]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"FUNCTION(postalCode, 'doubleValue', zipCode)"].mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"FUNCTION(postalCode, 'floatValue', zipCode)"].mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"FUNCTION(postalCode, 'decimalValue', zipCode)"].mgl_jsonExpressionObject, jsonExpression); + // NSExpression is unable to evaluate NSNumber’s -floatValue, + // -doubleValue, or -decimalValue by themselves because they each return + // a primitive instead of an object. + XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionWithFormat:@"FUNCTION(number, 'stringValue')"]; + NSArray *jsonExpression = @[@"to-string", @[@"get", @"number"]]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:@{@"number": @1.5} context:nil], @"1.5"); + XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression); + } +} + +- (void)testInterpolationExpressionObject { + { + NSDictionary *stops = @{@0: MGLConstantExpression(@100), @10: MGLConstantExpression(@200)}; + NSExpression *expression = [NSExpression expressionWithFormat:@"FUNCTION(x, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", stops]; + NSArray *jsonExpression = @[@"interpolate", @[@"linear"], @[@"get", @"x"], @0, @100, @10, @200]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression); + } + { + NSDictionary *stops = @{@1: MGLConstantExpression(@2), @3: MGLConstantExpression(@6)}; + NSExpression *expression = [NSExpression expressionWithFormat:@"FUNCTION(x, 'mgl_interpolateWithCurveType:parameters:stops:', 'exponential', 2, %@)", stops]; + NSArray *jsonExpression = @[@"interpolate", @[@"exponential", @2], @[@"get", @"x"], @1, @2, @3, @6]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression); + } + { + NSDictionary *stops = @{@0: MGLConstantExpression(@0), @100: MGLConstantExpression(@100)}; + NSExpression *expression = [NSExpression expressionWithFormat:@"FUNCTION(x, 'mgl_interpolateWithCurveType:parameters:stops:', 'cubic-bezier', { 0.42, 0, 0.58, 1 }, %@)", stops]; + NSArray *jsonExpression = @[@"interpolate", @[@"cubic-bezier", @0.42, @0, @0.58, @1], @[@"get", @"x"], @0, @0, @100, @100]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression); + } + { + NSDictionary *stops = @{@0: MGLConstantExpression(@111), @1: MGLConstantExpression(@1111)}; + NSExpression *expression = [NSExpression expressionWithFormat:@"FUNCTION(x, 'mgl_stepWithMinimum:stops:', 11, %@)", stops]; + NSArray *jsonExpression = @[@"step", @[@"get", @"x"], @11, @0, @111, @1, @1111]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression); + } +} + +- (void)testConditionalExpressionObject { + { + NSPredicate *conditional = [NSPredicate predicateWithFormat:@"1 = 2"]; + NSExpression *trueExpression = [NSExpression expressionForConstantValue:@YES]; + NSExpression *falseExpression = [NSExpression expressionForConstantValue:@NO]; + NSExpression *expression = [NSExpression expressionForConditional:conditional trueExpression:trueExpression falseExpression:falseExpression]; + NSArray *jsonExpression = @[@"case", @[@"==", @1, @2], @YES, @NO]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"TERNARY(1 = 2, TRUE, FALSE)"].mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @NO); + XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionWithFormat:@"TERNARY(0 = 1, TRUE, TERNARY(1 = 2, TRUE, FALSE))"]; + NSArray *jsonExpression = @[@"case", @[@"==", @0, @1], @YES, @[@"==", @1, @2], @YES, @NO]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @NO); + XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression); + } +} + @end diff --git a/platform/darwin/test/MGLFillExtrusionStyleLayerTests.mm b/platform/darwin/test/MGLFillExtrusionStyleLayerTests.mm index 5d99c815ea..f5e26381d6 100644 --- a/platform/darwin/test/MGLFillExtrusionStyleLayerTests.mm +++ b/platform/darwin/test/MGLFillExtrusionStyleLayerTests.mm @@ -52,40 +52,44 @@ { XCTAssertTrue(rawLayer->getFillExtrusionBase().isUndefined(), @"fill-extrusion-base should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.fillExtrusionBase; + NSExpression *defaultExpression = layer.fillExtrusionBase; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.fillExtrusionBase = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.fillExtrusionBase = constantExpression; mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getFillExtrusionBase(), propertyValue, - @"Setting fillExtrusionBase to a constant value should update fill-extrusion-base."); - XCTAssertEqualObjects(layer.fillExtrusionBase, constantStyleValue, - @"fillExtrusionBase should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.fillExtrusionBase = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting fillExtrusionBase to a constant value expression should update fill-extrusion-base."); + XCTAssertEqualObjects(layer.fillExtrusionBase, constantExpression, + @"fillExtrusionBase should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.fillExtrusionBase = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getFillExtrusionBase(), propertyValue, - @"Setting fillExtrusionBase to a camera function should update fill-extrusion-base."); - XCTAssertEqualObjects(layer.fillExtrusionBase, functionStyleValue, - @"fillExtrusionBase should round-trip camera functions."); + @"Setting fillExtrusionBase to a camera expression should update fill-extrusion-base."); + XCTAssertEqualObjects(layer.fillExtrusionBase, functionExpression, + @"fillExtrusionBase should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.fillExtrusionBase = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@18: constantExpression}]; + layer.fillExtrusionBase = functionExpression; mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getFillExtrusionBase(), propertyValue, - @"Setting fillExtrusionBase to a source function should update fill-extrusion-base."); - XCTAssertEqualObjects(layer.fillExtrusionBase, functionStyleValue, - @"fillExtrusionBase should round-trip source functions."); + @"Setting fillExtrusionBase to a data expression should update fill-extrusion-base."); + XCTAssertEqualObjects(layer.fillExtrusionBase, functionExpression, + @"fillExtrusionBase should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.fillExtrusionBase = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + layer.fillExtrusionBase = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -93,15 +97,15 @@ propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getFillExtrusionBase(), propertyValue, - @"Setting fillExtrusionBase to a composite function should update fill-extrusion-base."); - XCTAssertEqualObjects(layer.fillExtrusionBase, functionStyleValue, - @"fillExtrusionBase should round-trip composite functions."); + @"Setting fillExtrusionBase to a camera-data expression should update fill-extrusion-base."); + XCTAssertEqualObjects(layer.fillExtrusionBase, functionExpression, + @"fillExtrusionBase should round-trip camera-data expressions."); layer.fillExtrusionBase = nil; XCTAssertTrue(rawLayer->getFillExtrusionBase().isUndefined(), @"Unsetting fillExtrusionBase should return fill-extrusion-base to the default value."); - XCTAssertEqualObjects(layer.fillExtrusionBase, defaultStyleValue, + XCTAssertEqualObjects(layer.fillExtrusionBase, defaultExpression, @"fillExtrusionBase should return the default value after being unset."); // Transition property test layer.fillExtrusionBaseTransition = transitionTest; @@ -118,40 +122,44 @@ { XCTAssertTrue(rawLayer->getFillExtrusionColor().isUndefined(), @"fill-extrusion-color should be unset initially."); - MGLStyleValue<MGLColor *> *defaultStyleValue = layer.fillExtrusionColor; + NSExpression *defaultExpression = layer.fillExtrusionColor; - MGLStyleValue<MGLColor *> *constantStyleValue = [MGLStyleValue<MGLColor *> valueWithRawValue:[MGLColor redColor]]; - layer.fillExtrusionColor = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + layer.fillExtrusionColor = constantExpression; mbgl::style::DataDrivenPropertyValue<mbgl::Color> propertyValue = { { 1, 0, 0, 1 } }; XCTAssertEqual(rawLayer->getFillExtrusionColor(), propertyValue, - @"Setting fillExtrusionColor to a constant value should update fill-extrusion-color."); - XCTAssertEqualObjects(layer.fillExtrusionColor, constantStyleValue, - @"fillExtrusionColor should round-trip constant values."); - - MGLStyleValue<MGLColor *> * functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.fillExtrusionColor = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::Color> intervalStops = { {{18, { 1, 0, 0, 1 }}} }; + @"Setting fillExtrusionColor to a constant value expression should update fill-extrusion-color."); + XCTAssertEqualObjects(layer.fillExtrusionColor, constantExpression, + @"fillExtrusionColor should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.fillExtrusionColor = functionExpression; + + mbgl::style::IntervalStops<mbgl::Color> intervalStops = {{ + { -INFINITY, { 1, 0, 0, 1 } }, + { 18, { 1, 0, 0, 1 } }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::Color> { intervalStops }; XCTAssertEqual(rawLayer->getFillExtrusionColor(), propertyValue, - @"Setting fillExtrusionColor to a camera function should update fill-extrusion-color."); - XCTAssertEqualObjects(layer.fillExtrusionColor, functionStyleValue, - @"fillExtrusionColor should round-trip camera functions."); + @"Setting fillExtrusionColor to a camera expression should update fill-extrusion-color."); + XCTAssertEqualObjects(layer.fillExtrusionColor, functionExpression, + @"fillExtrusionColor should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.fillExtrusionColor = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@18: constantExpression}]; + layer.fillExtrusionColor = functionExpression; mbgl::style::ExponentialStops<mbgl::Color> exponentialStops = { {{18, { 1, 0, 0, 1 }}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<mbgl::Color> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getFillExtrusionColor(), propertyValue, - @"Setting fillExtrusionColor to a source function should update fill-extrusion-color."); - XCTAssertEqualObjects(layer.fillExtrusionColor, functionStyleValue, - @"fillExtrusionColor should round-trip source functions."); + @"Setting fillExtrusionColor to a data expression should update fill-extrusion-color."); + XCTAssertEqualObjects(layer.fillExtrusionColor, functionExpression, + @"fillExtrusionColor should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.fillExtrusionColor = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + layer.fillExtrusionColor = functionExpression; std::map<float, mbgl::Color> innerStops { {18, { 1, 0, 0, 1 }} }; mbgl::style::CompositeExponentialStops<mbgl::Color> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -159,15 +167,15 @@ propertyValue = mbgl::style::CompositeFunction<mbgl::Color> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getFillExtrusionColor(), propertyValue, - @"Setting fillExtrusionColor to a composite function should update fill-extrusion-color."); - XCTAssertEqualObjects(layer.fillExtrusionColor, functionStyleValue, - @"fillExtrusionColor should round-trip composite functions."); + @"Setting fillExtrusionColor to a camera-data expression should update fill-extrusion-color."); + XCTAssertEqualObjects(layer.fillExtrusionColor, functionExpression, + @"fillExtrusionColor should round-trip camera-data expressions."); layer.fillExtrusionColor = nil; XCTAssertTrue(rawLayer->getFillExtrusionColor().isUndefined(), @"Unsetting fillExtrusionColor should return fill-extrusion-color to the default value."); - XCTAssertEqualObjects(layer.fillExtrusionColor, defaultStyleValue, + XCTAssertEqualObjects(layer.fillExtrusionColor, defaultExpression, @"fillExtrusionColor should return the default value after being unset."); // Transition property test layer.fillExtrusionColorTransition = transitionTest; @@ -184,40 +192,44 @@ { XCTAssertTrue(rawLayer->getFillExtrusionHeight().isUndefined(), @"fill-extrusion-height should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.fillExtrusionHeight; + NSExpression *defaultExpression = layer.fillExtrusionHeight; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.fillExtrusionHeight = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.fillExtrusionHeight = constantExpression; mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getFillExtrusionHeight(), propertyValue, - @"Setting fillExtrusionHeight to a constant value should update fill-extrusion-height."); - XCTAssertEqualObjects(layer.fillExtrusionHeight, constantStyleValue, - @"fillExtrusionHeight should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.fillExtrusionHeight = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting fillExtrusionHeight to a constant value expression should update fill-extrusion-height."); + XCTAssertEqualObjects(layer.fillExtrusionHeight, constantExpression, + @"fillExtrusionHeight should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.fillExtrusionHeight = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getFillExtrusionHeight(), propertyValue, - @"Setting fillExtrusionHeight to a camera function should update fill-extrusion-height."); - XCTAssertEqualObjects(layer.fillExtrusionHeight, functionStyleValue, - @"fillExtrusionHeight should round-trip camera functions."); + @"Setting fillExtrusionHeight to a camera expression should update fill-extrusion-height."); + XCTAssertEqualObjects(layer.fillExtrusionHeight, functionExpression, + @"fillExtrusionHeight should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.fillExtrusionHeight = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@18: constantExpression}]; + layer.fillExtrusionHeight = functionExpression; mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getFillExtrusionHeight(), propertyValue, - @"Setting fillExtrusionHeight to a source function should update fill-extrusion-height."); - XCTAssertEqualObjects(layer.fillExtrusionHeight, functionStyleValue, - @"fillExtrusionHeight should round-trip source functions."); + @"Setting fillExtrusionHeight to a data expression should update fill-extrusion-height."); + XCTAssertEqualObjects(layer.fillExtrusionHeight, functionExpression, + @"fillExtrusionHeight should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.fillExtrusionHeight = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + layer.fillExtrusionHeight = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -225,15 +237,15 @@ propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getFillExtrusionHeight(), propertyValue, - @"Setting fillExtrusionHeight to a composite function should update fill-extrusion-height."); - XCTAssertEqualObjects(layer.fillExtrusionHeight, functionStyleValue, - @"fillExtrusionHeight should round-trip composite functions."); + @"Setting fillExtrusionHeight to a camera-data expression should update fill-extrusion-height."); + XCTAssertEqualObjects(layer.fillExtrusionHeight, functionExpression, + @"fillExtrusionHeight should round-trip camera-data expressions."); layer.fillExtrusionHeight = nil; XCTAssertTrue(rawLayer->getFillExtrusionHeight().isUndefined(), @"Unsetting fillExtrusionHeight should return fill-extrusion-height to the default value."); - XCTAssertEqualObjects(layer.fillExtrusionHeight, defaultStyleValue, + XCTAssertEqualObjects(layer.fillExtrusionHeight, defaultExpression, @"fillExtrusionHeight should return the default value after being unset."); // Transition property test layer.fillExtrusionHeightTransition = transitionTest; @@ -250,39 +262,44 @@ { XCTAssertTrue(rawLayer->getFillExtrusionOpacity().isUndefined(), @"fill-extrusion-opacity should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.fillExtrusionOpacity; + NSExpression *defaultExpression = layer.fillExtrusionOpacity; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.fillExtrusionOpacity = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.fillExtrusionOpacity = constantExpression; mbgl::style::PropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getFillExtrusionOpacity(), propertyValue, - @"Setting fillExtrusionOpacity to a constant value should update fill-extrusion-opacity."); - XCTAssertEqualObjects(layer.fillExtrusionOpacity, constantStyleValue, - @"fillExtrusionOpacity should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.fillExtrusionOpacity = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting fillExtrusionOpacity to a constant value expression should update fill-extrusion-opacity."); + XCTAssertEqualObjects(layer.fillExtrusionOpacity, constantExpression, + @"fillExtrusionOpacity should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.fillExtrusionOpacity = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getFillExtrusionOpacity(), propertyValue, - @"Setting fillExtrusionOpacity to a camera function should update fill-extrusion-opacity."); - XCTAssertEqualObjects(layer.fillExtrusionOpacity, functionStyleValue, - @"fillExtrusionOpacity should round-trip camera functions."); + @"Setting fillExtrusionOpacity to a camera expression should update fill-extrusion-opacity."); + XCTAssertEqualObjects(layer.fillExtrusionOpacity, functionExpression, + @"fillExtrusionOpacity should round-trip camera expressions."); layer.fillExtrusionOpacity = nil; XCTAssertTrue(rawLayer->getFillExtrusionOpacity().isUndefined(), @"Unsetting fillExtrusionOpacity should return fill-extrusion-opacity to the default value."); - XCTAssertEqualObjects(layer.fillExtrusionOpacity, defaultStyleValue, + XCTAssertEqualObjects(layer.fillExtrusionOpacity, defaultExpression, @"fillExtrusionOpacity should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.fillExtrusionOpacity = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.fillExtrusionOpacity = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.fillExtrusionOpacity = functionExpression, NSException, NSInvalidArgumentException, @"MGLFillExtrusionLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.fillExtrusionOpacity = functionExpression, NSException, NSInvalidArgumentException, @"MGLFillExtrusionLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); // Transition property test layer.fillExtrusionOpacityTransition = transitionTest; auto toptions = rawLayer->getFillExtrusionOpacityTransition(); @@ -298,39 +315,44 @@ { XCTAssertTrue(rawLayer->getFillExtrusionPattern().isUndefined(), @"fill-extrusion-pattern should be unset initially."); - MGLStyleValue<NSString *> *defaultStyleValue = layer.fillExtrusionPattern; + NSExpression *defaultExpression = layer.fillExtrusionPattern; - MGLStyleValue<NSString *> *constantStyleValue = [MGLStyleValue<NSString *> valueWithRawValue:@"Fill Extrusion Pattern"]; - layer.fillExtrusionPattern = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"'Fill Extrusion Pattern'"]; + layer.fillExtrusionPattern = constantExpression; mbgl::style::PropertyValue<std::string> propertyValue = { "Fill Extrusion Pattern" }; XCTAssertEqual(rawLayer->getFillExtrusionPattern(), propertyValue, - @"Setting fillExtrusionPattern to a constant value should update fill-extrusion-pattern."); - XCTAssertEqualObjects(layer.fillExtrusionPattern, constantStyleValue, - @"fillExtrusionPattern should round-trip constant values."); - - MGLStyleValue<NSString *> * functionStyleValue = [MGLStyleValue<NSString *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.fillExtrusionPattern = functionStyleValue; - - mbgl::style::IntervalStops<std::string> intervalStops = { {{18, "Fill Extrusion Pattern"}} }; + @"Setting fillExtrusionPattern to a constant value expression should update fill-extrusion-pattern."); + XCTAssertEqualObjects(layer.fillExtrusionPattern, constantExpression, + @"fillExtrusionPattern should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"'Fill Extrusion Pattern'"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.fillExtrusionPattern = functionExpression; + + mbgl::style::IntervalStops<std::string> intervalStops = {{ + { -INFINITY, "Fill Extrusion Pattern" }, + { 18, "Fill Extrusion Pattern" }, + }}; propertyValue = mbgl::style::CameraFunction<std::string> { intervalStops }; XCTAssertEqual(rawLayer->getFillExtrusionPattern(), propertyValue, - @"Setting fillExtrusionPattern to a camera function should update fill-extrusion-pattern."); - XCTAssertEqualObjects(layer.fillExtrusionPattern, functionStyleValue, - @"fillExtrusionPattern should round-trip camera functions."); + @"Setting fillExtrusionPattern to a camera expression should update fill-extrusion-pattern."); + XCTAssertEqualObjects(layer.fillExtrusionPattern, functionExpression, + @"fillExtrusionPattern should round-trip camera expressions."); layer.fillExtrusionPattern = nil; XCTAssertTrue(rawLayer->getFillExtrusionPattern().isUndefined(), @"Unsetting fillExtrusionPattern should return fill-extrusion-pattern to the default value."); - XCTAssertEqualObjects(layer.fillExtrusionPattern, defaultStyleValue, + XCTAssertEqualObjects(layer.fillExtrusionPattern, defaultExpression, @"fillExtrusionPattern should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSString *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.fillExtrusionPattern = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSString *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.fillExtrusionPattern = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.fillExtrusionPattern = functionExpression, NSException, NSInvalidArgumentException, @"MGLFillExtrusionLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.fillExtrusionPattern = functionExpression, NSException, NSInvalidArgumentException, @"MGLFillExtrusionLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); // Transition property test layer.fillExtrusionPatternTransition = transitionTest; auto toptions = rawLayer->getFillExtrusionPatternTransition(); @@ -346,84 +368,94 @@ { XCTAssertTrue(rawLayer->getFillExtrusionTranslate().isUndefined(), @"fill-extrusion-translate should be unset initially."); - MGLStyleValue<NSValue *> *defaultStyleValue = layer.fillExtrusionTranslation; + NSExpression *defaultExpression = layer.fillExtrusionTranslation; - MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue: + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", #if TARGET_OS_IPHONE [NSValue valueWithCGVector:CGVectorMake(1, 1)] #else [NSValue valueWithMGLVector:CGVectorMake(1, -1)] #endif ]; - layer.fillExtrusionTranslation = constantStyleValue; + layer.fillExtrusionTranslation = constantExpression; mbgl::style::PropertyValue<std::array<float, 2>> propertyValue = { { 1, 1 } }; XCTAssertEqual(rawLayer->getFillExtrusionTranslate(), propertyValue, - @"Setting fillExtrusionTranslation to a constant value should update fill-extrusion-translate."); - XCTAssertEqualObjects(layer.fillExtrusionTranslation, constantStyleValue, - @"fillExtrusionTranslation should round-trip constant values."); - - MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.fillExtrusionTranslation = functionStyleValue; - - mbgl::style::IntervalStops<std::array<float, 2>> intervalStops = { {{18, { 1, 1 }}} }; + @"Setting fillExtrusionTranslation to a constant value expression should update fill-extrusion-translate."); + XCTAssertEqualObjects(layer.fillExtrusionTranslation, constantExpression, + @"fillExtrusionTranslation should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"{1, 1}"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.fillExtrusionTranslation = functionExpression; + + mbgl::style::IntervalStops<std::array<float, 2>> intervalStops = {{ + { -INFINITY, { 1, 1 } }, + { 18, { 1, 1 } }, + }}; propertyValue = mbgl::style::CameraFunction<std::array<float, 2>> { intervalStops }; XCTAssertEqual(rawLayer->getFillExtrusionTranslate(), propertyValue, - @"Setting fillExtrusionTranslation to a camera function should update fill-extrusion-translate."); - XCTAssertEqualObjects(layer.fillExtrusionTranslation, functionStyleValue, - @"fillExtrusionTranslation should round-trip camera functions."); + @"Setting fillExtrusionTranslation to a camera expression should update fill-extrusion-translate."); + XCTAssertEqualObjects(layer.fillExtrusionTranslation, functionExpression, + @"fillExtrusionTranslation should round-trip camera expressions."); layer.fillExtrusionTranslation = nil; XCTAssertTrue(rawLayer->getFillExtrusionTranslate().isUndefined(), @"Unsetting fillExtrusionTranslation should return fill-extrusion-translate to the default value."); - XCTAssertEqualObjects(layer.fillExtrusionTranslation, defaultStyleValue, + XCTAssertEqualObjects(layer.fillExtrusionTranslation, defaultExpression, @"fillExtrusionTranslation should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.fillExtrusionTranslation = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.fillExtrusionTranslation = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.fillExtrusionTranslation = functionExpression, NSException, NSInvalidArgumentException, @"MGLFillExtrusionLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.fillExtrusionTranslation = functionExpression, NSException, NSInvalidArgumentException, @"MGLFillExtrusionLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // fill-extrusion-translate-anchor { XCTAssertTrue(rawLayer->getFillExtrusionTranslateAnchor().isUndefined(), @"fill-extrusion-translate-anchor should be unset initially."); - MGLStyleValue<NSValue *> *defaultStyleValue = layer.fillExtrusionTranslationAnchor; + NSExpression *defaultExpression = layer.fillExtrusionTranslationAnchor; - MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue:[NSValue valueWithMGLFillExtrusionTranslationAnchor:MGLFillExtrusionTranslationAnchorViewport]]; - layer.fillExtrusionTranslationAnchor = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"'viewport'"]; + layer.fillExtrusionTranslationAnchor = constantExpression; mbgl::style::PropertyValue<mbgl::style::TranslateAnchorType> propertyValue = { mbgl::style::TranslateAnchorType::Viewport }; XCTAssertEqual(rawLayer->getFillExtrusionTranslateAnchor(), propertyValue, - @"Setting fillExtrusionTranslationAnchor to a constant value should update fill-extrusion-translate-anchor."); - XCTAssertEqualObjects(layer.fillExtrusionTranslationAnchor, constantStyleValue, - @"fillExtrusionTranslationAnchor should round-trip constant values."); - - MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.fillExtrusionTranslationAnchor = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::style::TranslateAnchorType> intervalStops = { {{18, mbgl::style::TranslateAnchorType::Viewport}} }; + @"Setting fillExtrusionTranslationAnchor to a constant value expression should update fill-extrusion-translate-anchor."); + XCTAssertEqualObjects(layer.fillExtrusionTranslationAnchor, constantExpression, + @"fillExtrusionTranslationAnchor should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"'viewport'"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.fillExtrusionTranslationAnchor = functionExpression; + + mbgl::style::IntervalStops<mbgl::style::TranslateAnchorType> intervalStops = {{ + { -INFINITY, mbgl::style::TranslateAnchorType::Viewport }, + { 18, mbgl::style::TranslateAnchorType::Viewport }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::style::TranslateAnchorType> { intervalStops }; XCTAssertEqual(rawLayer->getFillExtrusionTranslateAnchor(), propertyValue, - @"Setting fillExtrusionTranslationAnchor to a camera function should update fill-extrusion-translate-anchor."); - XCTAssertEqualObjects(layer.fillExtrusionTranslationAnchor, functionStyleValue, - @"fillExtrusionTranslationAnchor should round-trip camera functions."); + @"Setting fillExtrusionTranslationAnchor to a camera expression should update fill-extrusion-translate-anchor."); + XCTAssertEqualObjects(layer.fillExtrusionTranslationAnchor, functionExpression, + @"fillExtrusionTranslationAnchor should round-trip camera expressions."); layer.fillExtrusionTranslationAnchor = nil; XCTAssertTrue(rawLayer->getFillExtrusionTranslateAnchor().isUndefined(), @"Unsetting fillExtrusionTranslationAnchor should return fill-extrusion-translate-anchor to the default value."); - XCTAssertEqualObjects(layer.fillExtrusionTranslationAnchor, defaultStyleValue, + XCTAssertEqualObjects(layer.fillExtrusionTranslationAnchor, defaultExpression, @"fillExtrusionTranslationAnchor should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.fillExtrusionTranslationAnchor = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.fillExtrusionTranslationAnchor = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.fillExtrusionTranslationAnchor = functionExpression, NSException, NSInvalidArgumentException, @"MGLFillExtrusionLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.fillExtrusionTranslationAnchor = functionExpression, NSException, NSInvalidArgumentException, @"MGLFillExtrusionLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } } diff --git a/platform/darwin/test/MGLFillStyleLayerTests.mm b/platform/darwin/test/MGLFillStyleLayerTests.mm index 85f0b24fa7..25521f3ede 100644 --- a/platform/darwin/test/MGLFillStyleLayerTests.mm +++ b/platform/darwin/test/MGLFillStyleLayerTests.mm @@ -52,79 +52,88 @@ { XCTAssertTrue(rawLayer->getFillAntialias().isUndefined(), @"fill-antialias should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.fillAntialiased; + NSExpression *defaultExpression = layer.fillAntialiased; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@NO]; - layer.fillAntialiased = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"false"]; + layer.fillAntialiased = constantExpression; mbgl::style::PropertyValue<bool> propertyValue = { false }; XCTAssertEqual(rawLayer->getFillAntialias(), propertyValue, - @"Setting fillAntialiased to a constant value should update fill-antialias."); - XCTAssertEqualObjects(layer.fillAntialiased, constantStyleValue, - @"fillAntialiased should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.fillAntialiased = functionStyleValue; - - mbgl::style::IntervalStops<bool> intervalStops = { {{18, false}} }; + @"Setting fillAntialiased to a constant value expression should update fill-antialias."); + XCTAssertEqualObjects(layer.fillAntialiased, constantExpression, + @"fillAntialiased should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"false"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.fillAntialiased = functionExpression; + + mbgl::style::IntervalStops<bool> intervalStops = {{ + { -INFINITY, false }, + { 18, false }, + }}; propertyValue = mbgl::style::CameraFunction<bool> { intervalStops }; XCTAssertEqual(rawLayer->getFillAntialias(), propertyValue, - @"Setting fillAntialiased to a camera function should update fill-antialias."); - XCTAssertEqualObjects(layer.fillAntialiased, functionStyleValue, - @"fillAntialiased should round-trip camera functions."); + @"Setting fillAntialiased to a camera expression should update fill-antialias."); + XCTAssertEqualObjects(layer.fillAntialiased, functionExpression, + @"fillAntialiased should round-trip camera expressions."); layer.fillAntialiased = nil; XCTAssertTrue(rawLayer->getFillAntialias().isUndefined(), @"Unsetting fillAntialiased should return fill-antialias to the default value."); - XCTAssertEqualObjects(layer.fillAntialiased, defaultStyleValue, + XCTAssertEqualObjects(layer.fillAntialiased, defaultExpression, @"fillAntialiased should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.fillAntialiased = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.fillAntialiased = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.fillAntialiased = functionExpression, NSException, NSInvalidArgumentException, @"MGLFillLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.fillAntialiased = functionExpression, NSException, NSInvalidArgumentException, @"MGLFillLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // fill-color { XCTAssertTrue(rawLayer->getFillColor().isUndefined(), @"fill-color should be unset initially."); - MGLStyleValue<MGLColor *> *defaultStyleValue = layer.fillColor; + NSExpression *defaultExpression = layer.fillColor; - MGLStyleValue<MGLColor *> *constantStyleValue = [MGLStyleValue<MGLColor *> valueWithRawValue:[MGLColor redColor]]; - layer.fillColor = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + layer.fillColor = constantExpression; mbgl::style::DataDrivenPropertyValue<mbgl::Color> propertyValue = { { 1, 0, 0, 1 } }; XCTAssertEqual(rawLayer->getFillColor(), propertyValue, - @"Setting fillColor to a constant value should update fill-color."); - XCTAssertEqualObjects(layer.fillColor, constantStyleValue, - @"fillColor should round-trip constant values."); - - MGLStyleValue<MGLColor *> * functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.fillColor = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::Color> intervalStops = { {{18, { 1, 0, 0, 1 }}} }; + @"Setting fillColor to a constant value expression should update fill-color."); + XCTAssertEqualObjects(layer.fillColor, constantExpression, + @"fillColor should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.fillColor = functionExpression; + + mbgl::style::IntervalStops<mbgl::Color> intervalStops = {{ + { -INFINITY, { 1, 0, 0, 1 } }, + { 18, { 1, 0, 0, 1 } }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::Color> { intervalStops }; XCTAssertEqual(rawLayer->getFillColor(), propertyValue, - @"Setting fillColor to a camera function should update fill-color."); - XCTAssertEqualObjects(layer.fillColor, functionStyleValue, - @"fillColor should round-trip camera functions."); + @"Setting fillColor to a camera expression should update fill-color."); + XCTAssertEqualObjects(layer.fillColor, functionExpression, + @"fillColor should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.fillColor = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@18: constantExpression}]; + layer.fillColor = functionExpression; mbgl::style::ExponentialStops<mbgl::Color> exponentialStops = { {{18, { 1, 0, 0, 1 }}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<mbgl::Color> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getFillColor(), propertyValue, - @"Setting fillColor to a source function should update fill-color."); - XCTAssertEqualObjects(layer.fillColor, functionStyleValue, - @"fillColor should round-trip source functions."); + @"Setting fillColor to a data expression should update fill-color."); + XCTAssertEqualObjects(layer.fillColor, functionExpression, + @"fillColor should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.fillColor = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + layer.fillColor = functionExpression; std::map<float, mbgl::Color> innerStops { {18, { 1, 0, 0, 1 }} }; mbgl::style::CompositeExponentialStops<mbgl::Color> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -132,15 +141,15 @@ propertyValue = mbgl::style::CompositeFunction<mbgl::Color> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getFillColor(), propertyValue, - @"Setting fillColor to a composite function should update fill-color."); - XCTAssertEqualObjects(layer.fillColor, functionStyleValue, - @"fillColor should round-trip composite functions."); + @"Setting fillColor to a camera-data expression should update fill-color."); + XCTAssertEqualObjects(layer.fillColor, functionExpression, + @"fillColor should round-trip camera-data expressions."); layer.fillColor = nil; XCTAssertTrue(rawLayer->getFillColor().isUndefined(), @"Unsetting fillColor should return fill-color to the default value."); - XCTAssertEqualObjects(layer.fillColor, defaultStyleValue, + XCTAssertEqualObjects(layer.fillColor, defaultExpression, @"fillColor should return the default value after being unset."); // Transition property test layer.fillColorTransition = transitionTest; @@ -157,40 +166,44 @@ { XCTAssertTrue(rawLayer->getFillOpacity().isUndefined(), @"fill-opacity should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.fillOpacity; + NSExpression *defaultExpression = layer.fillOpacity; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.fillOpacity = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.fillOpacity = constantExpression; mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getFillOpacity(), propertyValue, - @"Setting fillOpacity to a constant value should update fill-opacity."); - XCTAssertEqualObjects(layer.fillOpacity, constantStyleValue, - @"fillOpacity should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.fillOpacity = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting fillOpacity to a constant value expression should update fill-opacity."); + XCTAssertEqualObjects(layer.fillOpacity, constantExpression, + @"fillOpacity should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.fillOpacity = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getFillOpacity(), propertyValue, - @"Setting fillOpacity to a camera function should update fill-opacity."); - XCTAssertEqualObjects(layer.fillOpacity, functionStyleValue, - @"fillOpacity should round-trip camera functions."); + @"Setting fillOpacity to a camera expression should update fill-opacity."); + XCTAssertEqualObjects(layer.fillOpacity, functionExpression, + @"fillOpacity should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.fillOpacity = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@18: constantExpression}]; + layer.fillOpacity = functionExpression; mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getFillOpacity(), propertyValue, - @"Setting fillOpacity to a source function should update fill-opacity."); - XCTAssertEqualObjects(layer.fillOpacity, functionStyleValue, - @"fillOpacity should round-trip source functions."); + @"Setting fillOpacity to a data expression should update fill-opacity."); + XCTAssertEqualObjects(layer.fillOpacity, functionExpression, + @"fillOpacity should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.fillOpacity = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + layer.fillOpacity = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -198,15 +211,15 @@ propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getFillOpacity(), propertyValue, - @"Setting fillOpacity to a composite function should update fill-opacity."); - XCTAssertEqualObjects(layer.fillOpacity, functionStyleValue, - @"fillOpacity should round-trip composite functions."); + @"Setting fillOpacity to a camera-data expression should update fill-opacity."); + XCTAssertEqualObjects(layer.fillOpacity, functionExpression, + @"fillOpacity should round-trip camera-data expressions."); layer.fillOpacity = nil; XCTAssertTrue(rawLayer->getFillOpacity().isUndefined(), @"Unsetting fillOpacity should return fill-opacity to the default value."); - XCTAssertEqualObjects(layer.fillOpacity, defaultStyleValue, + XCTAssertEqualObjects(layer.fillOpacity, defaultExpression, @"fillOpacity should return the default value after being unset."); // Transition property test layer.fillOpacityTransition = transitionTest; @@ -223,40 +236,44 @@ { XCTAssertTrue(rawLayer->getFillOutlineColor().isUndefined(), @"fill-outline-color should be unset initially."); - MGLStyleValue<MGLColor *> *defaultStyleValue = layer.fillOutlineColor; + NSExpression *defaultExpression = layer.fillOutlineColor; - MGLStyleValue<MGLColor *> *constantStyleValue = [MGLStyleValue<MGLColor *> valueWithRawValue:[MGLColor redColor]]; - layer.fillOutlineColor = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + layer.fillOutlineColor = constantExpression; mbgl::style::DataDrivenPropertyValue<mbgl::Color> propertyValue = { { 1, 0, 0, 1 } }; XCTAssertEqual(rawLayer->getFillOutlineColor(), propertyValue, - @"Setting fillOutlineColor to a constant value should update fill-outline-color."); - XCTAssertEqualObjects(layer.fillOutlineColor, constantStyleValue, - @"fillOutlineColor should round-trip constant values."); - - MGLStyleValue<MGLColor *> * functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.fillOutlineColor = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::Color> intervalStops = { {{18, { 1, 0, 0, 1 }}} }; + @"Setting fillOutlineColor to a constant value expression should update fill-outline-color."); + XCTAssertEqualObjects(layer.fillOutlineColor, constantExpression, + @"fillOutlineColor should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.fillOutlineColor = functionExpression; + + mbgl::style::IntervalStops<mbgl::Color> intervalStops = {{ + { -INFINITY, { 1, 0, 0, 1 } }, + { 18, { 1, 0, 0, 1 } }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::Color> { intervalStops }; XCTAssertEqual(rawLayer->getFillOutlineColor(), propertyValue, - @"Setting fillOutlineColor to a camera function should update fill-outline-color."); - XCTAssertEqualObjects(layer.fillOutlineColor, functionStyleValue, - @"fillOutlineColor should round-trip camera functions."); + @"Setting fillOutlineColor to a camera expression should update fill-outline-color."); + XCTAssertEqualObjects(layer.fillOutlineColor, functionExpression, + @"fillOutlineColor should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.fillOutlineColor = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@18: constantExpression}]; + layer.fillOutlineColor = functionExpression; mbgl::style::ExponentialStops<mbgl::Color> exponentialStops = { {{18, { 1, 0, 0, 1 }}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<mbgl::Color> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getFillOutlineColor(), propertyValue, - @"Setting fillOutlineColor to a source function should update fill-outline-color."); - XCTAssertEqualObjects(layer.fillOutlineColor, functionStyleValue, - @"fillOutlineColor should round-trip source functions."); + @"Setting fillOutlineColor to a data expression should update fill-outline-color."); + XCTAssertEqualObjects(layer.fillOutlineColor, functionExpression, + @"fillOutlineColor should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.fillOutlineColor = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + layer.fillOutlineColor = functionExpression; std::map<float, mbgl::Color> innerStops { {18, { 1, 0, 0, 1 }} }; mbgl::style::CompositeExponentialStops<mbgl::Color> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -264,15 +281,15 @@ propertyValue = mbgl::style::CompositeFunction<mbgl::Color> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getFillOutlineColor(), propertyValue, - @"Setting fillOutlineColor to a composite function should update fill-outline-color."); - XCTAssertEqualObjects(layer.fillOutlineColor, functionStyleValue, - @"fillOutlineColor should round-trip composite functions."); + @"Setting fillOutlineColor to a camera-data expression should update fill-outline-color."); + XCTAssertEqualObjects(layer.fillOutlineColor, functionExpression, + @"fillOutlineColor should round-trip camera-data expressions."); layer.fillOutlineColor = nil; XCTAssertTrue(rawLayer->getFillOutlineColor().isUndefined(), @"Unsetting fillOutlineColor should return fill-outline-color to the default value."); - XCTAssertEqualObjects(layer.fillOutlineColor, defaultStyleValue, + XCTAssertEqualObjects(layer.fillOutlineColor, defaultExpression, @"fillOutlineColor should return the default value after being unset."); // Transition property test layer.fillOutlineColorTransition = transitionTest; @@ -289,39 +306,44 @@ { XCTAssertTrue(rawLayer->getFillPattern().isUndefined(), @"fill-pattern should be unset initially."); - MGLStyleValue<NSString *> *defaultStyleValue = layer.fillPattern; + NSExpression *defaultExpression = layer.fillPattern; - MGLStyleValue<NSString *> *constantStyleValue = [MGLStyleValue<NSString *> valueWithRawValue:@"Fill Pattern"]; - layer.fillPattern = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"'Fill Pattern'"]; + layer.fillPattern = constantExpression; mbgl::style::PropertyValue<std::string> propertyValue = { "Fill Pattern" }; XCTAssertEqual(rawLayer->getFillPattern(), propertyValue, - @"Setting fillPattern to a constant value should update fill-pattern."); - XCTAssertEqualObjects(layer.fillPattern, constantStyleValue, - @"fillPattern should round-trip constant values."); - - MGLStyleValue<NSString *> * functionStyleValue = [MGLStyleValue<NSString *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.fillPattern = functionStyleValue; - - mbgl::style::IntervalStops<std::string> intervalStops = { {{18, "Fill Pattern"}} }; + @"Setting fillPattern to a constant value expression should update fill-pattern."); + XCTAssertEqualObjects(layer.fillPattern, constantExpression, + @"fillPattern should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"'Fill Pattern'"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.fillPattern = functionExpression; + + mbgl::style::IntervalStops<std::string> intervalStops = {{ + { -INFINITY, "Fill Pattern" }, + { 18, "Fill Pattern" }, + }}; propertyValue = mbgl::style::CameraFunction<std::string> { intervalStops }; XCTAssertEqual(rawLayer->getFillPattern(), propertyValue, - @"Setting fillPattern to a camera function should update fill-pattern."); - XCTAssertEqualObjects(layer.fillPattern, functionStyleValue, - @"fillPattern should round-trip camera functions."); + @"Setting fillPattern to a camera expression should update fill-pattern."); + XCTAssertEqualObjects(layer.fillPattern, functionExpression, + @"fillPattern should round-trip camera expressions."); layer.fillPattern = nil; XCTAssertTrue(rawLayer->getFillPattern().isUndefined(), @"Unsetting fillPattern should return fill-pattern to the default value."); - XCTAssertEqualObjects(layer.fillPattern, defaultStyleValue, + XCTAssertEqualObjects(layer.fillPattern, defaultExpression, @"fillPattern should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSString *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.fillPattern = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSString *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.fillPattern = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.fillPattern = functionExpression, NSException, NSInvalidArgumentException, @"MGLFillLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.fillPattern = functionExpression, NSException, NSInvalidArgumentException, @"MGLFillLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); // Transition property test layer.fillPatternTransition = transitionTest; auto toptions = rawLayer->getFillPatternTransition(); @@ -337,84 +359,94 @@ { XCTAssertTrue(rawLayer->getFillTranslate().isUndefined(), @"fill-translate should be unset initially."); - MGLStyleValue<NSValue *> *defaultStyleValue = layer.fillTranslation; + NSExpression *defaultExpression = layer.fillTranslation; - MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue: + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", #if TARGET_OS_IPHONE [NSValue valueWithCGVector:CGVectorMake(1, 1)] #else [NSValue valueWithMGLVector:CGVectorMake(1, -1)] #endif ]; - layer.fillTranslation = constantStyleValue; + layer.fillTranslation = constantExpression; mbgl::style::PropertyValue<std::array<float, 2>> propertyValue = { { 1, 1 } }; XCTAssertEqual(rawLayer->getFillTranslate(), propertyValue, - @"Setting fillTranslation to a constant value should update fill-translate."); - XCTAssertEqualObjects(layer.fillTranslation, constantStyleValue, - @"fillTranslation should round-trip constant values."); - - MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.fillTranslation = functionStyleValue; - - mbgl::style::IntervalStops<std::array<float, 2>> intervalStops = { {{18, { 1, 1 }}} }; + @"Setting fillTranslation to a constant value expression should update fill-translate."); + XCTAssertEqualObjects(layer.fillTranslation, constantExpression, + @"fillTranslation should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"{1, 1}"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.fillTranslation = functionExpression; + + mbgl::style::IntervalStops<std::array<float, 2>> intervalStops = {{ + { -INFINITY, { 1, 1 } }, + { 18, { 1, 1 } }, + }}; propertyValue = mbgl::style::CameraFunction<std::array<float, 2>> { intervalStops }; XCTAssertEqual(rawLayer->getFillTranslate(), propertyValue, - @"Setting fillTranslation to a camera function should update fill-translate."); - XCTAssertEqualObjects(layer.fillTranslation, functionStyleValue, - @"fillTranslation should round-trip camera functions."); + @"Setting fillTranslation to a camera expression should update fill-translate."); + XCTAssertEqualObjects(layer.fillTranslation, functionExpression, + @"fillTranslation should round-trip camera expressions."); layer.fillTranslation = nil; XCTAssertTrue(rawLayer->getFillTranslate().isUndefined(), @"Unsetting fillTranslation should return fill-translate to the default value."); - XCTAssertEqualObjects(layer.fillTranslation, defaultStyleValue, + XCTAssertEqualObjects(layer.fillTranslation, defaultExpression, @"fillTranslation should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.fillTranslation = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.fillTranslation = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.fillTranslation = functionExpression, NSException, NSInvalidArgumentException, @"MGLFillLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.fillTranslation = functionExpression, NSException, NSInvalidArgumentException, @"MGLFillLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // fill-translate-anchor { XCTAssertTrue(rawLayer->getFillTranslateAnchor().isUndefined(), @"fill-translate-anchor should be unset initially."); - MGLStyleValue<NSValue *> *defaultStyleValue = layer.fillTranslationAnchor; + NSExpression *defaultExpression = layer.fillTranslationAnchor; - MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue:[NSValue valueWithMGLFillTranslationAnchor:MGLFillTranslationAnchorViewport]]; - layer.fillTranslationAnchor = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"'viewport'"]; + layer.fillTranslationAnchor = constantExpression; mbgl::style::PropertyValue<mbgl::style::TranslateAnchorType> propertyValue = { mbgl::style::TranslateAnchorType::Viewport }; XCTAssertEqual(rawLayer->getFillTranslateAnchor(), propertyValue, - @"Setting fillTranslationAnchor to a constant value should update fill-translate-anchor."); - XCTAssertEqualObjects(layer.fillTranslationAnchor, constantStyleValue, - @"fillTranslationAnchor should round-trip constant values."); - - MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.fillTranslationAnchor = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::style::TranslateAnchorType> intervalStops = { {{18, mbgl::style::TranslateAnchorType::Viewport}} }; + @"Setting fillTranslationAnchor to a constant value expression should update fill-translate-anchor."); + XCTAssertEqualObjects(layer.fillTranslationAnchor, constantExpression, + @"fillTranslationAnchor should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"'viewport'"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.fillTranslationAnchor = functionExpression; + + mbgl::style::IntervalStops<mbgl::style::TranslateAnchorType> intervalStops = {{ + { -INFINITY, mbgl::style::TranslateAnchorType::Viewport }, + { 18, mbgl::style::TranslateAnchorType::Viewport }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::style::TranslateAnchorType> { intervalStops }; XCTAssertEqual(rawLayer->getFillTranslateAnchor(), propertyValue, - @"Setting fillTranslationAnchor to a camera function should update fill-translate-anchor."); - XCTAssertEqualObjects(layer.fillTranslationAnchor, functionStyleValue, - @"fillTranslationAnchor should round-trip camera functions."); + @"Setting fillTranslationAnchor to a camera expression should update fill-translate-anchor."); + XCTAssertEqualObjects(layer.fillTranslationAnchor, functionExpression, + @"fillTranslationAnchor should round-trip camera expressions."); layer.fillTranslationAnchor = nil; XCTAssertTrue(rawLayer->getFillTranslateAnchor().isUndefined(), @"Unsetting fillTranslationAnchor should return fill-translate-anchor to the default value."); - XCTAssertEqualObjects(layer.fillTranslationAnchor, defaultStyleValue, + XCTAssertEqualObjects(layer.fillTranslationAnchor, defaultExpression, @"fillTranslationAnchor should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.fillTranslationAnchor = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.fillTranslationAnchor = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.fillTranslationAnchor = functionExpression, NSException, NSInvalidArgumentException, @"MGLFillLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.fillTranslationAnchor = functionExpression, NSException, NSInvalidArgumentException, @"MGLFillLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } } diff --git a/platform/darwin/test/MGLHillshadeStyleLayerTests.mm b/platform/darwin/test/MGLHillshadeStyleLayerTests.mm index 283830ccb5..87950e4f24 100644 --- a/platform/darwin/test/MGLHillshadeStyleLayerTests.mm +++ b/platform/darwin/test/MGLHillshadeStyleLayerTests.mm @@ -52,39 +52,44 @@ { XCTAssertTrue(rawLayer->getHillshadeAccentColor().isUndefined(), @"hillshade-accent-color should be unset initially."); - MGLStyleValue<MGLColor *> *defaultStyleValue = layer.hillshadeAccentColor; + NSExpression *defaultExpression = layer.hillshadeAccentColor; - MGLStyleValue<MGLColor *> *constantStyleValue = [MGLStyleValue<MGLColor *> valueWithRawValue:[MGLColor redColor]]; - layer.hillshadeAccentColor = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + layer.hillshadeAccentColor = constantExpression; mbgl::style::PropertyValue<mbgl::Color> propertyValue = { { 1, 0, 0, 1 } }; XCTAssertEqual(rawLayer->getHillshadeAccentColor(), propertyValue, - @"Setting hillshadeAccentColor to a constant value should update hillshade-accent-color."); - XCTAssertEqualObjects(layer.hillshadeAccentColor, constantStyleValue, - @"hillshadeAccentColor should round-trip constant values."); - - MGLStyleValue<MGLColor *> * functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.hillshadeAccentColor = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::Color> intervalStops = { {{18, { 1, 0, 0, 1 }}} }; + @"Setting hillshadeAccentColor to a constant value expression should update hillshade-accent-color."); + XCTAssertEqualObjects(layer.hillshadeAccentColor, constantExpression, + @"hillshadeAccentColor should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.hillshadeAccentColor = functionExpression; + + mbgl::style::IntervalStops<mbgl::Color> intervalStops = {{ + { -INFINITY, { 1, 0, 0, 1 } }, + { 18, { 1, 0, 0, 1 } }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::Color> { intervalStops }; XCTAssertEqual(rawLayer->getHillshadeAccentColor(), propertyValue, - @"Setting hillshadeAccentColor to a camera function should update hillshade-accent-color."); - XCTAssertEqualObjects(layer.hillshadeAccentColor, functionStyleValue, - @"hillshadeAccentColor should round-trip camera functions."); + @"Setting hillshadeAccentColor to a camera expression should update hillshade-accent-color."); + XCTAssertEqualObjects(layer.hillshadeAccentColor, functionExpression, + @"hillshadeAccentColor should round-trip camera expressions."); layer.hillshadeAccentColor = nil; XCTAssertTrue(rawLayer->getHillshadeAccentColor().isUndefined(), @"Unsetting hillshadeAccentColor should return hillshade-accent-color to the default value."); - XCTAssertEqualObjects(layer.hillshadeAccentColor, defaultStyleValue, + XCTAssertEqualObjects(layer.hillshadeAccentColor, defaultExpression, @"hillshadeAccentColor should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.hillshadeAccentColor = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.hillshadeAccentColor = 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.hillshadeAccentColor = functionExpression, NSException, NSInvalidArgumentException, @"MGLHillshadeLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.hillshadeAccentColor = functionExpression, NSException, NSInvalidArgumentException, @"MGLHillshadeLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); // Transition property test layer.hillshadeAccentColorTransition = transitionTest; auto toptions = rawLayer->getHillshadeAccentColorTransition(); @@ -100,39 +105,44 @@ { XCTAssertTrue(rawLayer->getHillshadeExaggeration().isUndefined(), @"hillshade-exaggeration should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.hillshadeExaggeration; + NSExpression *defaultExpression = layer.hillshadeExaggeration; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.hillshadeExaggeration = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.hillshadeExaggeration = constantExpression; mbgl::style::PropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getHillshadeExaggeration(), propertyValue, - @"Setting hillshadeExaggeration to a constant value should update hillshade-exaggeration."); - XCTAssertEqualObjects(layer.hillshadeExaggeration, constantStyleValue, - @"hillshadeExaggeration should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.hillshadeExaggeration = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting hillshadeExaggeration to a constant value expression should update hillshade-exaggeration."); + XCTAssertEqualObjects(layer.hillshadeExaggeration, constantExpression, + @"hillshadeExaggeration should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.hillshadeExaggeration = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getHillshadeExaggeration(), propertyValue, - @"Setting hillshadeExaggeration to a camera function should update hillshade-exaggeration."); - XCTAssertEqualObjects(layer.hillshadeExaggeration, functionStyleValue, - @"hillshadeExaggeration should round-trip camera functions."); + @"Setting hillshadeExaggeration to a camera expression should update hillshade-exaggeration."); + XCTAssertEqualObjects(layer.hillshadeExaggeration, functionExpression, + @"hillshadeExaggeration should round-trip camera expressions."); layer.hillshadeExaggeration = nil; XCTAssertTrue(rawLayer->getHillshadeExaggeration().isUndefined(), @"Unsetting hillshadeExaggeration should return hillshade-exaggeration to the default value."); - XCTAssertEqualObjects(layer.hillshadeExaggeration, defaultStyleValue, + XCTAssertEqualObjects(layer.hillshadeExaggeration, defaultExpression, @"hillshadeExaggeration should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.hillshadeExaggeration = 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.hillshadeExaggeration = 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.hillshadeExaggeration = functionExpression, NSException, NSInvalidArgumentException, @"MGLHillshadeLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.hillshadeExaggeration = functionExpression, NSException, NSInvalidArgumentException, @"MGLHillshadeLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); // Transition property test layer.hillshadeExaggerationTransition = transitionTest; auto toptions = rawLayer->getHillshadeExaggerationTransition(); @@ -148,39 +158,44 @@ { XCTAssertTrue(rawLayer->getHillshadeHighlightColor().isUndefined(), @"hillshade-highlight-color should be unset initially."); - MGLStyleValue<MGLColor *> *defaultStyleValue = layer.hillshadeHighlightColor; + NSExpression *defaultExpression = layer.hillshadeHighlightColor; - MGLStyleValue<MGLColor *> *constantStyleValue = [MGLStyleValue<MGLColor *> valueWithRawValue:[MGLColor redColor]]; - layer.hillshadeHighlightColor = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + layer.hillshadeHighlightColor = constantExpression; mbgl::style::PropertyValue<mbgl::Color> propertyValue = { { 1, 0, 0, 1 } }; XCTAssertEqual(rawLayer->getHillshadeHighlightColor(), propertyValue, - @"Setting hillshadeHighlightColor to a constant value should update hillshade-highlight-color."); - XCTAssertEqualObjects(layer.hillshadeHighlightColor, constantStyleValue, - @"hillshadeHighlightColor should round-trip constant values."); - - MGLStyleValue<MGLColor *> * functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.hillshadeHighlightColor = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::Color> intervalStops = { {{18, { 1, 0, 0, 1 }}} }; + @"Setting hillshadeHighlightColor to a constant value expression should update hillshade-highlight-color."); + XCTAssertEqualObjects(layer.hillshadeHighlightColor, constantExpression, + @"hillshadeHighlightColor should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.hillshadeHighlightColor = functionExpression; + + mbgl::style::IntervalStops<mbgl::Color> intervalStops = {{ + { -INFINITY, { 1, 0, 0, 1 } }, + { 18, { 1, 0, 0, 1 } }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::Color> { intervalStops }; XCTAssertEqual(rawLayer->getHillshadeHighlightColor(), propertyValue, - @"Setting hillshadeHighlightColor to a camera function should update hillshade-highlight-color."); - XCTAssertEqualObjects(layer.hillshadeHighlightColor, functionStyleValue, - @"hillshadeHighlightColor should round-trip camera functions."); + @"Setting hillshadeHighlightColor to a camera expression should update hillshade-highlight-color."); + XCTAssertEqualObjects(layer.hillshadeHighlightColor, functionExpression, + @"hillshadeHighlightColor should round-trip camera expressions."); layer.hillshadeHighlightColor = nil; XCTAssertTrue(rawLayer->getHillshadeHighlightColor().isUndefined(), @"Unsetting hillshadeHighlightColor should return hillshade-highlight-color to the default value."); - XCTAssertEqualObjects(layer.hillshadeHighlightColor, defaultStyleValue, + XCTAssertEqualObjects(layer.hillshadeHighlightColor, defaultExpression, @"hillshadeHighlightColor should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.hillshadeHighlightColor = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.hillshadeHighlightColor = 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.hillshadeHighlightColor = functionExpression, NSException, NSInvalidArgumentException, @"MGLHillshadeLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.hillshadeHighlightColor = functionExpression, NSException, NSInvalidArgumentException, @"MGLHillshadeLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); // Transition property test layer.hillshadeHighlightColorTransition = transitionTest; auto toptions = rawLayer->getHillshadeHighlightColorTransition(); @@ -196,78 +211,88 @@ { XCTAssertTrue(rawLayer->getHillshadeIlluminationAnchor().isUndefined(), @"hillshade-illumination-anchor should be unset initially."); - MGLStyleValue<NSValue *> *defaultStyleValue = layer.hillshadeIlluminationAnchor; + NSExpression *defaultExpression = layer.hillshadeIlluminationAnchor; - MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue:[NSValue valueWithMGLHillshadeIlluminationAnchor:MGLHillshadeIlluminationAnchorViewport]]; - layer.hillshadeIlluminationAnchor = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"'viewport'"]; + layer.hillshadeIlluminationAnchor = constantExpression; mbgl::style::PropertyValue<mbgl::style::HillshadeIlluminationAnchorType> propertyValue = { mbgl::style::HillshadeIlluminationAnchorType::Viewport }; XCTAssertEqual(rawLayer->getHillshadeIlluminationAnchor(), propertyValue, - @"Setting hillshadeIlluminationAnchor to a constant value should update hillshade-illumination-anchor."); - XCTAssertEqualObjects(layer.hillshadeIlluminationAnchor, constantStyleValue, - @"hillshadeIlluminationAnchor should round-trip constant values."); - - MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.hillshadeIlluminationAnchor = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::style::HillshadeIlluminationAnchorType> intervalStops = { {{18, mbgl::style::HillshadeIlluminationAnchorType::Viewport}} }; + @"Setting hillshadeIlluminationAnchor to a constant value expression should update hillshade-illumination-anchor."); + XCTAssertEqualObjects(layer.hillshadeIlluminationAnchor, constantExpression, + @"hillshadeIlluminationAnchor should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"'viewport'"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.hillshadeIlluminationAnchor = functionExpression; + + mbgl::style::IntervalStops<mbgl::style::HillshadeIlluminationAnchorType> intervalStops = {{ + { -INFINITY, mbgl::style::HillshadeIlluminationAnchorType::Viewport }, + { 18, mbgl::style::HillshadeIlluminationAnchorType::Viewport }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::style::HillshadeIlluminationAnchorType> { intervalStops }; XCTAssertEqual(rawLayer->getHillshadeIlluminationAnchor(), propertyValue, - @"Setting hillshadeIlluminationAnchor to a camera function should update hillshade-illumination-anchor."); - XCTAssertEqualObjects(layer.hillshadeIlluminationAnchor, functionStyleValue, - @"hillshadeIlluminationAnchor should round-trip camera functions."); + @"Setting hillshadeIlluminationAnchor to a camera expression should update hillshade-illumination-anchor."); + XCTAssertEqualObjects(layer.hillshadeIlluminationAnchor, functionExpression, + @"hillshadeIlluminationAnchor should round-trip camera expressions."); layer.hillshadeIlluminationAnchor = nil; XCTAssertTrue(rawLayer->getHillshadeIlluminationAnchor().isUndefined(), @"Unsetting hillshadeIlluminationAnchor should return hillshade-illumination-anchor to the default value."); - XCTAssertEqualObjects(layer.hillshadeIlluminationAnchor, defaultStyleValue, + XCTAssertEqualObjects(layer.hillshadeIlluminationAnchor, defaultExpression, @"hillshadeIlluminationAnchor should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.hillshadeIlluminationAnchor = 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.hillshadeIlluminationAnchor = 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.hillshadeIlluminationAnchor = functionExpression, NSException, NSInvalidArgumentException, @"MGLHillshadeLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.hillshadeIlluminationAnchor = functionExpression, NSException, NSInvalidArgumentException, @"MGLHillshadeLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // hillshade-illumination-direction { XCTAssertTrue(rawLayer->getHillshadeIlluminationDirection().isUndefined(), @"hillshade-illumination-direction should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.hillshadeIlluminationDirection; + NSExpression *defaultExpression = layer.hillshadeIlluminationDirection; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.hillshadeIlluminationDirection = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.hillshadeIlluminationDirection = constantExpression; mbgl::style::PropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getHillshadeIlluminationDirection(), propertyValue, - @"Setting hillshadeIlluminationDirection to a constant value should update hillshade-illumination-direction."); - XCTAssertEqualObjects(layer.hillshadeIlluminationDirection, constantStyleValue, - @"hillshadeIlluminationDirection should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.hillshadeIlluminationDirection = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting hillshadeIlluminationDirection to a constant value expression should update hillshade-illumination-direction."); + XCTAssertEqualObjects(layer.hillshadeIlluminationDirection, constantExpression, + @"hillshadeIlluminationDirection should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.hillshadeIlluminationDirection = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getHillshadeIlluminationDirection(), propertyValue, - @"Setting hillshadeIlluminationDirection to a camera function should update hillshade-illumination-direction."); - XCTAssertEqualObjects(layer.hillshadeIlluminationDirection, functionStyleValue, - @"hillshadeIlluminationDirection should round-trip camera functions."); + @"Setting hillshadeIlluminationDirection to a camera expression should update hillshade-illumination-direction."); + XCTAssertEqualObjects(layer.hillshadeIlluminationDirection, functionExpression, + @"hillshadeIlluminationDirection should round-trip camera expressions."); layer.hillshadeIlluminationDirection = nil; XCTAssertTrue(rawLayer->getHillshadeIlluminationDirection().isUndefined(), @"Unsetting hillshadeIlluminationDirection should return hillshade-illumination-direction to the default value."); - XCTAssertEqualObjects(layer.hillshadeIlluminationDirection, defaultStyleValue, + XCTAssertEqualObjects(layer.hillshadeIlluminationDirection, defaultExpression, @"hillshadeIlluminationDirection should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.hillshadeIlluminationDirection = 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.hillshadeIlluminationDirection = 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.hillshadeIlluminationDirection = functionExpression, NSException, NSInvalidArgumentException, @"MGLHillshadeLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.hillshadeIlluminationDirection = functionExpression, NSException, NSInvalidArgumentException, @"MGLHillshadeLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); // Transition property test layer.hillshadeIlluminationDirectionTransition = transitionTest; auto toptions = rawLayer->getHillshadeIlluminationDirectionTransition(); @@ -283,39 +308,44 @@ { XCTAssertTrue(rawLayer->getHillshadeShadowColor().isUndefined(), @"hillshade-shadow-color should be unset initially."); - MGLStyleValue<MGLColor *> *defaultStyleValue = layer.hillshadeShadowColor; + NSExpression *defaultExpression = layer.hillshadeShadowColor; - MGLStyleValue<MGLColor *> *constantStyleValue = [MGLStyleValue<MGLColor *> valueWithRawValue:[MGLColor redColor]]; - layer.hillshadeShadowColor = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + layer.hillshadeShadowColor = constantExpression; mbgl::style::PropertyValue<mbgl::Color> propertyValue = { { 1, 0, 0, 1 } }; XCTAssertEqual(rawLayer->getHillshadeShadowColor(), propertyValue, - @"Setting hillshadeShadowColor to a constant value should update hillshade-shadow-color."); - XCTAssertEqualObjects(layer.hillshadeShadowColor, constantStyleValue, - @"hillshadeShadowColor should round-trip constant values."); - - MGLStyleValue<MGLColor *> * functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.hillshadeShadowColor = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::Color> intervalStops = { {{18, { 1, 0, 0, 1 }}} }; + @"Setting hillshadeShadowColor to a constant value expression should update hillshade-shadow-color."); + XCTAssertEqualObjects(layer.hillshadeShadowColor, constantExpression, + @"hillshadeShadowColor should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.hillshadeShadowColor = functionExpression; + + mbgl::style::IntervalStops<mbgl::Color> intervalStops = {{ + { -INFINITY, { 1, 0, 0, 1 } }, + { 18, { 1, 0, 0, 1 } }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::Color> { intervalStops }; XCTAssertEqual(rawLayer->getHillshadeShadowColor(), propertyValue, - @"Setting hillshadeShadowColor to a camera function should update hillshade-shadow-color."); - XCTAssertEqualObjects(layer.hillshadeShadowColor, functionStyleValue, - @"hillshadeShadowColor should round-trip camera functions."); + @"Setting hillshadeShadowColor to a camera expression should update hillshade-shadow-color."); + XCTAssertEqualObjects(layer.hillshadeShadowColor, functionExpression, + @"hillshadeShadowColor should round-trip camera expressions."); layer.hillshadeShadowColor = nil; XCTAssertTrue(rawLayer->getHillshadeShadowColor().isUndefined(), @"Unsetting hillshadeShadowColor should return hillshade-shadow-color to the default value."); - XCTAssertEqualObjects(layer.hillshadeShadowColor, defaultStyleValue, + XCTAssertEqualObjects(layer.hillshadeShadowColor, defaultExpression, @"hillshadeShadowColor should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.hillshadeShadowColor = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.hillshadeShadowColor = 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.hillshadeShadowColor = functionExpression, NSException, NSInvalidArgumentException, @"MGLHillshadeLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.hillshadeShadowColor = functionExpression, NSException, NSInvalidArgumentException, @"MGLHillshadeLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); // Transition property test layer.hillshadeShadowColorTransition = transitionTest; auto toptions = rawLayer->getHillshadeShadowColorTransition(); diff --git a/platform/darwin/test/MGLLightTest.mm b/platform/darwin/test/MGLLightTest.mm index de64d57851..a51c59c725 100644 --- a/platform/darwin/test/MGLLightTest.mm +++ b/platform/darwin/test/MGLLightTest.mm @@ -27,29 +27,28 @@ { mbgl::style::Light light; MGLLight *mglLight = [[MGLLight alloc] initWithMBGLLight:&light]; - auto lightFromMGLlight = [mglLight mbglLight]; + auto lightFromMGLLight = mglLight.mbglLight; - XCTAssertEqual(light.getDefaultAnchor(), lightFromMGLlight.getAnchor()); - XCTAssert([mglLight.anchor isKindOfClass:[MGLConstantStyleValue class]], @"mglLight.anchor isn’t a MGLConstantStyleValue."); - NSValue *anchorValue = ((MGLConstantStyleValue *)mglLight.anchor).rawValue; - XCTAssertEqual(anchorValue.MGLLightAnchorValue, MGLLightAnchorViewport); + XCTAssertEqual(light.getDefaultAnchor(), lightFromMGLLight.getAnchor()); + XCTAssertEqual(mglLight.anchor.expressionType, NSConstantValueExpressionType, @"mglLight.anchor isn’t a constant value expression."); + XCTAssertEqualObjects(mglLight.anchor.constantValue, @"viewport"); mbgl::style::PropertyValue<mbgl::style::LightAnchorType> propertyValue = { mbgl::style::LightAnchorType::Viewport }; light.setAnchor(propertyValue); mglLight = [[MGLLight alloc] initWithMBGLLight:&light]; - lightFromMGLlight = [mglLight mbglLight]; + lightFromMGLLight = mglLight.mbglLight; - XCTAssertEqual(light.getAnchor(), lightFromMGLlight.getAnchor()); + XCTAssertEqual(light.getAnchor(), lightFromMGLLight.getAnchor()); } // position { mbgl::style::Light light; MGLLight *mglLight = [[MGLLight alloc] initWithMBGLLight:&light]; - auto lightFromMGLlight = [mglLight mbglLight]; + auto lightFromMGLLight = mglLight.mbglLight; - XCTAssertEqual(light.getDefaultPosition(), lightFromMGLlight.getPosition()); - auto positionTransition = lightFromMGLlight.getPositionTransition(); + XCTAssertEqual(light.getDefaultPosition(), lightFromMGLLight.getPosition()); + auto positionTransition = lightFromMGLLight.getPositionTransition(); XCTAssert(positionTransition.delay && MGLTimeIntervalFromDuration(*positionTransition.delay) == defaultTransition.delay); XCTAssert(positionTransition.duration && MGLTimeIntervalFromDuration(*positionTransition.duration) == defaultTransition.duration); @@ -60,10 +59,10 @@ light.setPositionTransition(transitionOptions); mglLight = [[MGLLight alloc] initWithMBGLLight:&light]; - lightFromMGLlight = [mglLight mbglLight]; + lightFromMGLLight = mglLight.mbglLight; - XCTAssertEqual(light.getPosition(), lightFromMGLlight.getPosition()); - positionTransition = lightFromMGLlight.getPositionTransition(); + XCTAssertEqual(light.getPosition(), lightFromMGLLight.getPosition()); + positionTransition = lightFromMGLLight.getPositionTransition(); XCTAssert(positionTransition.delay && MGLTimeIntervalFromDuration(*positionTransition.delay) == transition.delay); XCTAssert(positionTransition.duration && MGLTimeIntervalFromDuration(*positionTransition.duration) == transition.duration); @@ -73,10 +72,10 @@ { mbgl::style::Light light; MGLLight *mglLight = [[MGLLight alloc] initWithMBGLLight:&light]; - auto lightFromMGLlight = [mglLight mbglLight]; + auto lightFromMGLLight = mglLight.mbglLight; - XCTAssertEqual(light.getDefaultColor(), lightFromMGLlight.getColor()); - auto colorTransition = lightFromMGLlight.getColorTransition(); + XCTAssertEqual(light.getDefaultColor(), lightFromMGLLight.getColor()); + auto colorTransition = lightFromMGLLight.getColorTransition(); XCTAssert(colorTransition.delay && MGLTimeIntervalFromDuration(*colorTransition.delay) == defaultTransition.delay); XCTAssert(colorTransition.duration && MGLTimeIntervalFromDuration(*colorTransition.duration) == defaultTransition.duration); @@ -85,10 +84,10 @@ light.setColorTransition(transitionOptions); mglLight = [[MGLLight alloc] initWithMBGLLight:&light]; - lightFromMGLlight = [mglLight mbglLight]; + lightFromMGLLight = mglLight.mbglLight; - XCTAssertEqual(light.getColor(), lightFromMGLlight.getColor()); - colorTransition = lightFromMGLlight.getColorTransition(); + XCTAssertEqual(light.getColor(), lightFromMGLLight.getColor()); + colorTransition = lightFromMGLLight.getColorTransition(); XCTAssert(colorTransition.delay && MGLTimeIntervalFromDuration(*colorTransition.delay) == transition.delay); XCTAssert(colorTransition.duration && MGLTimeIntervalFromDuration(*colorTransition.duration) == transition.duration); @@ -98,10 +97,10 @@ { mbgl::style::Light light; MGLLight *mglLight = [[MGLLight alloc] initWithMBGLLight:&light]; - auto lightFromMGLlight = [mglLight mbglLight]; + auto lightFromMGLLight = mglLight.mbglLight; - XCTAssertEqual(light.getDefaultIntensity(), lightFromMGLlight.getIntensity()); - auto intensityTransition = lightFromMGLlight.getIntensityTransition(); + XCTAssertEqual(light.getDefaultIntensity(), lightFromMGLLight.getIntensity()); + auto intensityTransition = lightFromMGLLight.getIntensityTransition(); XCTAssert(intensityTransition.delay && MGLTimeIntervalFromDuration(*intensityTransition.delay) == defaultTransition.delay); XCTAssert(intensityTransition.duration && MGLTimeIntervalFromDuration(*intensityTransition.duration) == defaultTransition.duration); @@ -110,10 +109,10 @@ light.setIntensityTransition(transitionOptions); mglLight = [[MGLLight alloc] initWithMBGLLight:&light]; - lightFromMGLlight = [mglLight mbglLight]; + lightFromMGLLight = mglLight.mbglLight; - XCTAssertEqual(light.getIntensity(), lightFromMGLlight.getIntensity()); - intensityTransition = lightFromMGLlight.getIntensityTransition(); + XCTAssertEqual(light.getIntensity(), lightFromMGLLight.getIntensity()); + intensityTransition = lightFromMGLLight.getIntensityTransition(); XCTAssert(intensityTransition.delay && MGLTimeIntervalFromDuration(*intensityTransition.delay) == transition.delay); XCTAssert(intensityTransition.duration && MGLTimeIntervalFromDuration(*intensityTransition.duration) == transition.duration); diff --git a/platform/darwin/test/MGLLightTest.mm.ejs b/platform/darwin/test/MGLLightTest.mm.ejs index 5b1f27d8d1..35ff58b6d5 100644 --- a/platform/darwin/test/MGLLightTest.mm.ejs +++ b/platform/darwin/test/MGLLightTest.mm.ejs @@ -32,19 +32,18 @@ { mbgl::style::Light light; MGLLight *mglLight = [[MGLLight alloc] initWithMBGLLight:&light]; - auto lightFromMGLlight = [mglLight mbglLight]; + auto lightFromMGLLight = mglLight.mbglLight; - XCTAssertEqual(light.getDefault<%- camelize(property.name) -%>(), lightFromMGLlight.get<%- camelize(property.name) -%>()); + XCTAssertEqual(light.getDefault<%- camelize(property.name) -%>(), lightFromMGLLight.get<%- camelize(property.name) -%>()); <% if (property.transition) { -%> - auto <%- camelizeWithLeadingLowercase(property.name) -%>Transition = lightFromMGLlight.get<%- camelize(property.name) -%>Transition(); + auto <%- camelizeWithLeadingLowercase(property.name) -%>Transition = lightFromMGLLight.get<%- camelize(property.name) -%>Transition(); XCTAssert(<%- camelizeWithLeadingLowercase(property.name) -%>Transition.delay && MGLTimeIntervalFromDuration(*<%- camelizeWithLeadingLowercase(property.name) -%>Transition.delay) == defaultTransition.delay); XCTAssert(<%- camelizeWithLeadingLowercase(property.name) -%>Transition.duration && MGLTimeIntervalFromDuration(*<%- camelizeWithLeadingLowercase(property.name) -%>Transition.duration) == defaultTransition.duration); <% } -%> <% if (property.type == "enum" && property.default) { -%> - XCTAssert([mglLight.<%- camelizeWithLeadingLowercase(property.name) -%> isKindOfClass:[MGLConstantStyleValue class]], @"mglLight.<%- camelizeWithLeadingLowercase(property.name) -%> isn’t a MGLConstantStyleValue."); - NSValue *<%- camelizeWithLeadingLowercase(property.name) -%>Value = ((MGLConstantStyleValue *)mglLight.<%- camelizeWithLeadingLowercase(property.name) -%>).rawValue; - XCTAssertEqual(<%- camelizeWithLeadingLowercase(property.name) -%>Value.MGLLight<%- camelize(property.name) -%>Value, MGLLight<%- camelize(property.name) -%><%- camelize(property.default) -%>); + XCTAssertEqual(mglLight.<%- camelizeWithLeadingLowercase(property.name) -%>.expressionType, NSConstantValueExpressionType, @"mglLight.<%- camelizeWithLeadingLowercase(property.name) -%> isn’t a constant value expression."); + XCTAssertEqualObjects(mglLight.<%- camelizeWithLeadingLowercase(property.name) -%>.constantValue, @"<%- property.default -%>"); <% } -%> <% if (property.type == "array") { -%> @@ -60,11 +59,11 @@ <% } -%> mglLight = [[MGLLight alloc] initWithMBGLLight:&light]; - lightFromMGLlight = [mglLight mbglLight]; + lightFromMGLLight = mglLight.mbglLight; - XCTAssertEqual(light.get<%- camelize(property.name) -%>(), lightFromMGLlight.get<%- camelize(property.name) -%>()); + XCTAssertEqual(light.get<%- camelize(property.name) -%>(), lightFromMGLLight.get<%- camelize(property.name) -%>()); <% if (property.transition) { -%> - <%- camelizeWithLeadingLowercase(property.name) -%>Transition = lightFromMGLlight.get<%- camelize(property.name) -%>Transition(); + <%- camelizeWithLeadingLowercase(property.name) -%>Transition = lightFromMGLLight.get<%- camelize(property.name) -%>Transition(); XCTAssert(<%- camelizeWithLeadingLowercase(property.name) -%>Transition.delay && MGLTimeIntervalFromDuration(*<%- camelizeWithLeadingLowercase(property.name) -%>Transition.delay) == transition.delay); XCTAssert(<%- camelizeWithLeadingLowercase(property.name) -%>Transition.duration && MGLTimeIntervalFromDuration(*<%- camelizeWithLeadingLowercase(property.name) -%>Transition.duration) == transition.duration); diff --git a/platform/darwin/test/MGLLineStyleLayerTests.mm b/platform/darwin/test/MGLLineStyleLayerTests.mm index 7e7926e22e..bf98e98320 100644 --- a/platform/darwin/test/MGLLineStyleLayerTests.mm +++ b/platform/darwin/test/MGLLineStyleLayerTests.mm @@ -52,72 +52,81 @@ { XCTAssertTrue(rawLayer->getLineCap().isUndefined(), @"line-cap should be unset initially."); - MGLStyleValue<NSValue *> *defaultStyleValue = layer.lineCap; + NSExpression *defaultExpression = layer.lineCap; - MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue:[NSValue valueWithMGLLineCap:MGLLineCapSquare]]; - layer.lineCap = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"'square'"]; + layer.lineCap = constantExpression; mbgl::style::PropertyValue<mbgl::style::LineCapType> propertyValue = { mbgl::style::LineCapType::Square }; XCTAssertEqual(rawLayer->getLineCap(), propertyValue, - @"Setting lineCap to a constant value should update line-cap."); - XCTAssertEqualObjects(layer.lineCap, constantStyleValue, - @"lineCap should round-trip constant values."); - - MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.lineCap = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::style::LineCapType> intervalStops = { {{18, mbgl::style::LineCapType::Square}} }; + @"Setting lineCap to a constant value expression should update line-cap."); + XCTAssertEqualObjects(layer.lineCap, constantExpression, + @"lineCap should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"'square'"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.lineCap = functionExpression; + + mbgl::style::IntervalStops<mbgl::style::LineCapType> intervalStops = {{ + { -INFINITY, mbgl::style::LineCapType::Square }, + { 18, mbgl::style::LineCapType::Square }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::style::LineCapType> { intervalStops }; XCTAssertEqual(rawLayer->getLineCap(), propertyValue, - @"Setting lineCap to a camera function should update line-cap."); - XCTAssertEqualObjects(layer.lineCap, functionStyleValue, - @"lineCap should round-trip camera functions."); + @"Setting lineCap to a camera expression should update line-cap."); + XCTAssertEqualObjects(layer.lineCap, functionExpression, + @"lineCap should round-trip camera expressions."); layer.lineCap = nil; XCTAssertTrue(rawLayer->getLineCap().isUndefined(), @"Unsetting lineCap should return line-cap to the default value."); - XCTAssertEqualObjects(layer.lineCap, defaultStyleValue, + XCTAssertEqualObjects(layer.lineCap, defaultExpression, @"lineCap should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.lineCap = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.lineCap = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.lineCap = functionExpression, NSException, NSInvalidArgumentException, @"MGLLineLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.lineCap = functionExpression, NSException, NSInvalidArgumentException, @"MGLLineLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // line-join { XCTAssertTrue(rawLayer->getLineJoin().isUndefined(), @"line-join should be unset initially."); - MGLStyleValue<NSValue *> *defaultStyleValue = layer.lineJoin; + NSExpression *defaultExpression = layer.lineJoin; - MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue:[NSValue valueWithMGLLineJoin:MGLLineJoinMiter]]; - layer.lineJoin = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"'miter'"]; + layer.lineJoin = constantExpression; mbgl::style::DataDrivenPropertyValue<mbgl::style::LineJoinType> propertyValue = { mbgl::style::LineJoinType::Miter }; XCTAssertEqual(rawLayer->getLineJoin(), propertyValue, - @"Setting lineJoin to a constant value should update line-join."); - XCTAssertEqualObjects(layer.lineJoin, constantStyleValue, - @"lineJoin should round-trip constant values."); - - MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.lineJoin = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::style::LineJoinType> intervalStops = { {{18, mbgl::style::LineJoinType::Miter}} }; + @"Setting lineJoin to a constant value expression should update line-join."); + XCTAssertEqualObjects(layer.lineJoin, constantExpression, + @"lineJoin should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"'miter'"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.lineJoin = functionExpression; + + mbgl::style::IntervalStops<mbgl::style::LineJoinType> intervalStops = {{ + { -INFINITY, mbgl::style::LineJoinType::Miter }, + { 18, mbgl::style::LineJoinType::Miter }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::style::LineJoinType> { intervalStops }; XCTAssertEqual(rawLayer->getLineJoin(), propertyValue, - @"Setting lineJoin to a camera function should update line-join."); - XCTAssertEqualObjects(layer.lineJoin, functionStyleValue, - @"lineJoin should round-trip camera functions."); + @"Setting lineJoin to a camera expression should update line-join."); + XCTAssertEqualObjects(layer.lineJoin, functionExpression, + @"lineJoin should round-trip camera expressions."); layer.lineJoin = nil; XCTAssertTrue(rawLayer->getLineJoin().isUndefined(), @"Unsetting lineJoin should return line-join to the default value."); - XCTAssertEqualObjects(layer.lineJoin, defaultStyleValue, + XCTAssertEqualObjects(layer.lineJoin, defaultExpression, @"lineJoin should return the default value after being unset."); } @@ -125,118 +134,132 @@ { XCTAssertTrue(rawLayer->getLineMiterLimit().isUndefined(), @"line-miter-limit should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.lineMiterLimit; + NSExpression *defaultExpression = layer.lineMiterLimit; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.lineMiterLimit = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.lineMiterLimit = constantExpression; mbgl::style::PropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getLineMiterLimit(), propertyValue, - @"Setting lineMiterLimit to a constant value should update line-miter-limit."); - XCTAssertEqualObjects(layer.lineMiterLimit, constantStyleValue, - @"lineMiterLimit should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.lineMiterLimit = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting lineMiterLimit to a constant value expression should update line-miter-limit."); + XCTAssertEqualObjects(layer.lineMiterLimit, constantExpression, + @"lineMiterLimit should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.lineMiterLimit = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getLineMiterLimit(), propertyValue, - @"Setting lineMiterLimit to a camera function should update line-miter-limit."); - XCTAssertEqualObjects(layer.lineMiterLimit, functionStyleValue, - @"lineMiterLimit should round-trip camera functions."); + @"Setting lineMiterLimit to a camera expression should update line-miter-limit."); + XCTAssertEqualObjects(layer.lineMiterLimit, functionExpression, + @"lineMiterLimit should round-trip camera expressions."); layer.lineMiterLimit = nil; XCTAssertTrue(rawLayer->getLineMiterLimit().isUndefined(), @"Unsetting lineMiterLimit should return line-miter-limit to the default value."); - XCTAssertEqualObjects(layer.lineMiterLimit, defaultStyleValue, + XCTAssertEqualObjects(layer.lineMiterLimit, defaultExpression, @"lineMiterLimit should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.lineMiterLimit = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.lineMiterLimit = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.lineMiterLimit = functionExpression, NSException, NSInvalidArgumentException, @"MGLLineLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.lineMiterLimit = functionExpression, NSException, NSInvalidArgumentException, @"MGLLineLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // line-round-limit { XCTAssertTrue(rawLayer->getLineRoundLimit().isUndefined(), @"line-round-limit should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.lineRoundLimit; + NSExpression *defaultExpression = layer.lineRoundLimit; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.lineRoundLimit = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.lineRoundLimit = constantExpression; mbgl::style::PropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getLineRoundLimit(), propertyValue, - @"Setting lineRoundLimit to a constant value should update line-round-limit."); - XCTAssertEqualObjects(layer.lineRoundLimit, constantStyleValue, - @"lineRoundLimit should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.lineRoundLimit = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting lineRoundLimit to a constant value expression should update line-round-limit."); + XCTAssertEqualObjects(layer.lineRoundLimit, constantExpression, + @"lineRoundLimit should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.lineRoundLimit = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getLineRoundLimit(), propertyValue, - @"Setting lineRoundLimit to a camera function should update line-round-limit."); - XCTAssertEqualObjects(layer.lineRoundLimit, functionStyleValue, - @"lineRoundLimit should round-trip camera functions."); + @"Setting lineRoundLimit to a camera expression should update line-round-limit."); + XCTAssertEqualObjects(layer.lineRoundLimit, functionExpression, + @"lineRoundLimit should round-trip camera expressions."); layer.lineRoundLimit = nil; XCTAssertTrue(rawLayer->getLineRoundLimit().isUndefined(), @"Unsetting lineRoundLimit should return line-round-limit to the default value."); - XCTAssertEqualObjects(layer.lineRoundLimit, defaultStyleValue, + XCTAssertEqualObjects(layer.lineRoundLimit, defaultExpression, @"lineRoundLimit should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.lineRoundLimit = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.lineRoundLimit = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.lineRoundLimit = functionExpression, NSException, NSInvalidArgumentException, @"MGLLineLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.lineRoundLimit = functionExpression, NSException, NSInvalidArgumentException, @"MGLLineLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // line-blur { XCTAssertTrue(rawLayer->getLineBlur().isUndefined(), @"line-blur should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.lineBlur; + NSExpression *defaultExpression = layer.lineBlur; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.lineBlur = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.lineBlur = constantExpression; mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getLineBlur(), propertyValue, - @"Setting lineBlur to a constant value should update line-blur."); - XCTAssertEqualObjects(layer.lineBlur, constantStyleValue, - @"lineBlur should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.lineBlur = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting lineBlur to a constant value expression should update line-blur."); + XCTAssertEqualObjects(layer.lineBlur, constantExpression, + @"lineBlur should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.lineBlur = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getLineBlur(), propertyValue, - @"Setting lineBlur to a camera function should update line-blur."); - XCTAssertEqualObjects(layer.lineBlur, functionStyleValue, - @"lineBlur should round-trip camera functions."); + @"Setting lineBlur to a camera expression should update line-blur."); + XCTAssertEqualObjects(layer.lineBlur, functionExpression, + @"lineBlur should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.lineBlur = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@18: constantExpression}]; + layer.lineBlur = functionExpression; mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getLineBlur(), propertyValue, - @"Setting lineBlur to a source function should update line-blur."); - XCTAssertEqualObjects(layer.lineBlur, functionStyleValue, - @"lineBlur should round-trip source functions."); + @"Setting lineBlur to a data expression should update line-blur."); + XCTAssertEqualObjects(layer.lineBlur, functionExpression, + @"lineBlur should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.lineBlur = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + layer.lineBlur = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -244,15 +267,15 @@ propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getLineBlur(), propertyValue, - @"Setting lineBlur to a composite function should update line-blur."); - XCTAssertEqualObjects(layer.lineBlur, functionStyleValue, - @"lineBlur should round-trip composite functions."); + @"Setting lineBlur to a camera-data expression should update line-blur."); + XCTAssertEqualObjects(layer.lineBlur, functionExpression, + @"lineBlur should round-trip camera-data expressions."); layer.lineBlur = nil; XCTAssertTrue(rawLayer->getLineBlur().isUndefined(), @"Unsetting lineBlur should return line-blur to the default value."); - XCTAssertEqualObjects(layer.lineBlur, defaultStyleValue, + XCTAssertEqualObjects(layer.lineBlur, defaultExpression, @"lineBlur should return the default value after being unset."); // Transition property test layer.lineBlurTransition = transitionTest; @@ -269,40 +292,44 @@ { XCTAssertTrue(rawLayer->getLineColor().isUndefined(), @"line-color should be unset initially."); - MGLStyleValue<MGLColor *> *defaultStyleValue = layer.lineColor; + NSExpression *defaultExpression = layer.lineColor; - MGLStyleValue<MGLColor *> *constantStyleValue = [MGLStyleValue<MGLColor *> valueWithRawValue:[MGLColor redColor]]; - layer.lineColor = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + layer.lineColor = constantExpression; mbgl::style::DataDrivenPropertyValue<mbgl::Color> propertyValue = { { 1, 0, 0, 1 } }; XCTAssertEqual(rawLayer->getLineColor(), propertyValue, - @"Setting lineColor to a constant value should update line-color."); - XCTAssertEqualObjects(layer.lineColor, constantStyleValue, - @"lineColor should round-trip constant values."); - - MGLStyleValue<MGLColor *> * functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.lineColor = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::Color> intervalStops = { {{18, { 1, 0, 0, 1 }}} }; + @"Setting lineColor to a constant value expression should update line-color."); + XCTAssertEqualObjects(layer.lineColor, constantExpression, + @"lineColor should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.lineColor = functionExpression; + + mbgl::style::IntervalStops<mbgl::Color> intervalStops = {{ + { -INFINITY, { 1, 0, 0, 1 } }, + { 18, { 1, 0, 0, 1 } }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::Color> { intervalStops }; XCTAssertEqual(rawLayer->getLineColor(), propertyValue, - @"Setting lineColor to a camera function should update line-color."); - XCTAssertEqualObjects(layer.lineColor, functionStyleValue, - @"lineColor should round-trip camera functions."); + @"Setting lineColor to a camera expression should update line-color."); + XCTAssertEqualObjects(layer.lineColor, functionExpression, + @"lineColor should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.lineColor = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@18: constantExpression}]; + layer.lineColor = functionExpression; mbgl::style::ExponentialStops<mbgl::Color> exponentialStops = { {{18, { 1, 0, 0, 1 }}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<mbgl::Color> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getLineColor(), propertyValue, - @"Setting lineColor to a source function should update line-color."); - XCTAssertEqualObjects(layer.lineColor, functionStyleValue, - @"lineColor should round-trip source functions."); + @"Setting lineColor to a data expression should update line-color."); + XCTAssertEqualObjects(layer.lineColor, functionExpression, + @"lineColor should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.lineColor = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + layer.lineColor = functionExpression; std::map<float, mbgl::Color> innerStops { {18, { 1, 0, 0, 1 }} }; mbgl::style::CompositeExponentialStops<mbgl::Color> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -310,15 +337,15 @@ propertyValue = mbgl::style::CompositeFunction<mbgl::Color> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getLineColor(), propertyValue, - @"Setting lineColor to a composite function should update line-color."); - XCTAssertEqualObjects(layer.lineColor, functionStyleValue, - @"lineColor should round-trip composite functions."); + @"Setting lineColor to a camera-data expression should update line-color."); + XCTAssertEqualObjects(layer.lineColor, functionExpression, + @"lineColor should round-trip camera-data expressions."); layer.lineColor = nil; XCTAssertTrue(rawLayer->getLineColor().isUndefined(), @"Unsetting lineColor should return line-color to the default value."); - XCTAssertEqualObjects(layer.lineColor, defaultStyleValue, + XCTAssertEqualObjects(layer.lineColor, defaultExpression, @"lineColor should return the default value after being unset."); // Transition property test layer.lineColorTransition = transitionTest; @@ -335,79 +362,88 @@ { XCTAssertTrue(rawLayer->getLineDasharray().isUndefined(), @"line-dasharray should be unset initially."); - MGLStyleValue<NSArray<NSNumber *> *> *defaultStyleValue = layer.lineDashPattern; + NSExpression *defaultExpression = layer.lineDashPattern; - MGLStyleValue<NSArray<NSNumber *> *> *constantStyleValue = [MGLStyleValue<NSArray<NSNumber *> *> valueWithRawValue:@[@1, @2]]; - layer.lineDashPattern = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"{1, 2}"]; + layer.lineDashPattern = constantExpression; mbgl::style::PropertyValue<std::vector<float>> propertyValue = { {1, 2} }; XCTAssertEqual(rawLayer->getLineDasharray(), propertyValue, - @"Setting lineDashPattern to a constant value should update line-dasharray."); - XCTAssertEqualObjects(layer.lineDashPattern, constantStyleValue, - @"lineDashPattern should round-trip constant values."); - - MGLStyleValue<NSArray<NSNumber *> *> * functionStyleValue = [MGLStyleValue<NSArray<NSNumber *> *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.lineDashPattern = functionStyleValue; - - mbgl::style::IntervalStops<std::vector<float>> intervalStops = { {{18, {1, 2}}} }; + @"Setting lineDashPattern to a constant value expression should update line-dasharray."); + XCTAssertEqualObjects(layer.lineDashPattern, constantExpression, + @"lineDashPattern should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"{1, 2}"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.lineDashPattern = functionExpression; + + mbgl::style::IntervalStops<std::vector<float>> intervalStops = {{ + { -INFINITY, {1, 2} }, + { 18, {1, 2} }, + }}; propertyValue = mbgl::style::CameraFunction<std::vector<float>> { intervalStops }; XCTAssertEqual(rawLayer->getLineDasharray(), propertyValue, - @"Setting lineDashPattern to a camera function should update line-dasharray."); - XCTAssertEqualObjects(layer.lineDashPattern, functionStyleValue, - @"lineDashPattern should round-trip camera functions."); + @"Setting lineDashPattern to a camera expression should update line-dasharray."); + XCTAssertEqualObjects(layer.lineDashPattern, functionExpression, + @"lineDashPattern should round-trip camera expressions."); layer.lineDashPattern = nil; XCTAssertTrue(rawLayer->getLineDasharray().isUndefined(), @"Unsetting lineDashPattern should return line-dasharray to the default value."); - XCTAssertEqualObjects(layer.lineDashPattern, defaultStyleValue, + XCTAssertEqualObjects(layer.lineDashPattern, defaultExpression, @"lineDashPattern should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSArray<NSNumber *> *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.lineDashPattern = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSArray<NSNumber *> *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.lineDashPattern = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.lineDashPattern = functionExpression, NSException, NSInvalidArgumentException, @"MGLLineLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.lineDashPattern = functionExpression, NSException, NSInvalidArgumentException, @"MGLLineLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // line-gap-width { XCTAssertTrue(rawLayer->getLineGapWidth().isUndefined(), @"line-gap-width should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.lineGapWidth; + NSExpression *defaultExpression = layer.lineGapWidth; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.lineGapWidth = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.lineGapWidth = constantExpression; mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getLineGapWidth(), propertyValue, - @"Setting lineGapWidth to a constant value should update line-gap-width."); - XCTAssertEqualObjects(layer.lineGapWidth, constantStyleValue, - @"lineGapWidth should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.lineGapWidth = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting lineGapWidth to a constant value expression should update line-gap-width."); + XCTAssertEqualObjects(layer.lineGapWidth, constantExpression, + @"lineGapWidth should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.lineGapWidth = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getLineGapWidth(), propertyValue, - @"Setting lineGapWidth to a camera function should update line-gap-width."); - XCTAssertEqualObjects(layer.lineGapWidth, functionStyleValue, - @"lineGapWidth should round-trip camera functions."); + @"Setting lineGapWidth to a camera expression should update line-gap-width."); + XCTAssertEqualObjects(layer.lineGapWidth, functionExpression, + @"lineGapWidth should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.lineGapWidth = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@18: constantExpression}]; + layer.lineGapWidth = functionExpression; mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getLineGapWidth(), propertyValue, - @"Setting lineGapWidth to a source function should update line-gap-width."); - XCTAssertEqualObjects(layer.lineGapWidth, functionStyleValue, - @"lineGapWidth should round-trip source functions."); + @"Setting lineGapWidth to a data expression should update line-gap-width."); + XCTAssertEqualObjects(layer.lineGapWidth, functionExpression, + @"lineGapWidth should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.lineGapWidth = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + layer.lineGapWidth = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -415,15 +451,15 @@ propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getLineGapWidth(), propertyValue, - @"Setting lineGapWidth to a composite function should update line-gap-width."); - XCTAssertEqualObjects(layer.lineGapWidth, functionStyleValue, - @"lineGapWidth should round-trip composite functions."); + @"Setting lineGapWidth to a camera-data expression should update line-gap-width."); + XCTAssertEqualObjects(layer.lineGapWidth, functionExpression, + @"lineGapWidth should round-trip camera-data expressions."); layer.lineGapWidth = nil; XCTAssertTrue(rawLayer->getLineGapWidth().isUndefined(), @"Unsetting lineGapWidth should return line-gap-width to the default value."); - XCTAssertEqualObjects(layer.lineGapWidth, defaultStyleValue, + XCTAssertEqualObjects(layer.lineGapWidth, defaultExpression, @"lineGapWidth should return the default value after being unset."); // Transition property test layer.lineGapWidthTransition = transitionTest; @@ -440,40 +476,44 @@ { XCTAssertTrue(rawLayer->getLineOffset().isUndefined(), @"line-offset should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.lineOffset; + NSExpression *defaultExpression = layer.lineOffset; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.lineOffset = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.lineOffset = constantExpression; mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getLineOffset(), propertyValue, - @"Setting lineOffset to a constant value should update line-offset."); - XCTAssertEqualObjects(layer.lineOffset, constantStyleValue, - @"lineOffset should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.lineOffset = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting lineOffset to a constant value expression should update line-offset."); + XCTAssertEqualObjects(layer.lineOffset, constantExpression, + @"lineOffset should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.lineOffset = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getLineOffset(), propertyValue, - @"Setting lineOffset to a camera function should update line-offset."); - XCTAssertEqualObjects(layer.lineOffset, functionStyleValue, - @"lineOffset should round-trip camera functions."); + @"Setting lineOffset to a camera expression should update line-offset."); + XCTAssertEqualObjects(layer.lineOffset, functionExpression, + @"lineOffset should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.lineOffset = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@18: constantExpression}]; + layer.lineOffset = functionExpression; mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getLineOffset(), propertyValue, - @"Setting lineOffset to a source function should update line-offset."); - XCTAssertEqualObjects(layer.lineOffset, functionStyleValue, - @"lineOffset should round-trip source functions."); + @"Setting lineOffset to a data expression should update line-offset."); + XCTAssertEqualObjects(layer.lineOffset, functionExpression, + @"lineOffset should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.lineOffset = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + layer.lineOffset = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -481,15 +521,15 @@ propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getLineOffset(), propertyValue, - @"Setting lineOffset to a composite function should update line-offset."); - XCTAssertEqualObjects(layer.lineOffset, functionStyleValue, - @"lineOffset should round-trip composite functions."); + @"Setting lineOffset to a camera-data expression should update line-offset."); + XCTAssertEqualObjects(layer.lineOffset, functionExpression, + @"lineOffset should round-trip camera-data expressions."); layer.lineOffset = nil; XCTAssertTrue(rawLayer->getLineOffset().isUndefined(), @"Unsetting lineOffset should return line-offset to the default value."); - XCTAssertEqualObjects(layer.lineOffset, defaultStyleValue, + XCTAssertEqualObjects(layer.lineOffset, defaultExpression, @"lineOffset should return the default value after being unset."); // Transition property test layer.lineOffsetTransition = transitionTest; @@ -506,40 +546,44 @@ { XCTAssertTrue(rawLayer->getLineOpacity().isUndefined(), @"line-opacity should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.lineOpacity; + NSExpression *defaultExpression = layer.lineOpacity; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.lineOpacity = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.lineOpacity = constantExpression; mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getLineOpacity(), propertyValue, - @"Setting lineOpacity to a constant value should update line-opacity."); - XCTAssertEqualObjects(layer.lineOpacity, constantStyleValue, - @"lineOpacity should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.lineOpacity = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting lineOpacity to a constant value expression should update line-opacity."); + XCTAssertEqualObjects(layer.lineOpacity, constantExpression, + @"lineOpacity should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.lineOpacity = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getLineOpacity(), propertyValue, - @"Setting lineOpacity to a camera function should update line-opacity."); - XCTAssertEqualObjects(layer.lineOpacity, functionStyleValue, - @"lineOpacity should round-trip camera functions."); + @"Setting lineOpacity to a camera expression should update line-opacity."); + XCTAssertEqualObjects(layer.lineOpacity, functionExpression, + @"lineOpacity should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.lineOpacity = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@18: constantExpression}]; + layer.lineOpacity = functionExpression; mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getLineOpacity(), propertyValue, - @"Setting lineOpacity to a source function should update line-opacity."); - XCTAssertEqualObjects(layer.lineOpacity, functionStyleValue, - @"lineOpacity should round-trip source functions."); + @"Setting lineOpacity to a data expression should update line-opacity."); + XCTAssertEqualObjects(layer.lineOpacity, functionExpression, + @"lineOpacity should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.lineOpacity = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + layer.lineOpacity = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -547,15 +591,15 @@ propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getLineOpacity(), propertyValue, - @"Setting lineOpacity to a composite function should update line-opacity."); - XCTAssertEqualObjects(layer.lineOpacity, functionStyleValue, - @"lineOpacity should round-trip composite functions."); + @"Setting lineOpacity to a camera-data expression should update line-opacity."); + XCTAssertEqualObjects(layer.lineOpacity, functionExpression, + @"lineOpacity should round-trip camera-data expressions."); layer.lineOpacity = nil; XCTAssertTrue(rawLayer->getLineOpacity().isUndefined(), @"Unsetting lineOpacity should return line-opacity to the default value."); - XCTAssertEqualObjects(layer.lineOpacity, defaultStyleValue, + XCTAssertEqualObjects(layer.lineOpacity, defaultExpression, @"lineOpacity should return the default value after being unset."); // Transition property test layer.lineOpacityTransition = transitionTest; @@ -572,39 +616,44 @@ { XCTAssertTrue(rawLayer->getLinePattern().isUndefined(), @"line-pattern should be unset initially."); - MGLStyleValue<NSString *> *defaultStyleValue = layer.linePattern; + NSExpression *defaultExpression = layer.linePattern; - MGLStyleValue<NSString *> *constantStyleValue = [MGLStyleValue<NSString *> valueWithRawValue:@"Line Pattern"]; - layer.linePattern = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"'Line Pattern'"]; + layer.linePattern = constantExpression; mbgl::style::PropertyValue<std::string> propertyValue = { "Line Pattern" }; XCTAssertEqual(rawLayer->getLinePattern(), propertyValue, - @"Setting linePattern to a constant value should update line-pattern."); - XCTAssertEqualObjects(layer.linePattern, constantStyleValue, - @"linePattern should round-trip constant values."); - - MGLStyleValue<NSString *> * functionStyleValue = [MGLStyleValue<NSString *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.linePattern = functionStyleValue; - - mbgl::style::IntervalStops<std::string> intervalStops = { {{18, "Line Pattern"}} }; + @"Setting linePattern to a constant value expression should update line-pattern."); + XCTAssertEqualObjects(layer.linePattern, constantExpression, + @"linePattern should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"'Line Pattern'"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.linePattern = functionExpression; + + mbgl::style::IntervalStops<std::string> intervalStops = {{ + { -INFINITY, "Line Pattern" }, + { 18, "Line Pattern" }, + }}; propertyValue = mbgl::style::CameraFunction<std::string> { intervalStops }; XCTAssertEqual(rawLayer->getLinePattern(), propertyValue, - @"Setting linePattern to a camera function should update line-pattern."); - XCTAssertEqualObjects(layer.linePattern, functionStyleValue, - @"linePattern should round-trip camera functions."); + @"Setting linePattern to a camera expression should update line-pattern."); + XCTAssertEqualObjects(layer.linePattern, functionExpression, + @"linePattern should round-trip camera expressions."); layer.linePattern = nil; XCTAssertTrue(rawLayer->getLinePattern().isUndefined(), @"Unsetting linePattern should return line-pattern to the default value."); - XCTAssertEqualObjects(layer.linePattern, defaultStyleValue, + XCTAssertEqualObjects(layer.linePattern, defaultExpression, @"linePattern should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSString *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.linePattern = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSString *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.linePattern = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.linePattern = functionExpression, NSException, NSInvalidArgumentException, @"MGLLineLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.linePattern = functionExpression, NSException, NSInvalidArgumentException, @"MGLLineLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); // Transition property test layer.linePatternTransition = transitionTest; auto toptions = rawLayer->getLinePatternTransition(); @@ -620,124 +669,138 @@ { XCTAssertTrue(rawLayer->getLineTranslate().isUndefined(), @"line-translate should be unset initially."); - MGLStyleValue<NSValue *> *defaultStyleValue = layer.lineTranslation; + NSExpression *defaultExpression = layer.lineTranslation; - MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue: + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", #if TARGET_OS_IPHONE [NSValue valueWithCGVector:CGVectorMake(1, 1)] #else [NSValue valueWithMGLVector:CGVectorMake(1, -1)] #endif ]; - layer.lineTranslation = constantStyleValue; + layer.lineTranslation = constantExpression; mbgl::style::PropertyValue<std::array<float, 2>> propertyValue = { { 1, 1 } }; XCTAssertEqual(rawLayer->getLineTranslate(), propertyValue, - @"Setting lineTranslation to a constant value should update line-translate."); - XCTAssertEqualObjects(layer.lineTranslation, constantStyleValue, - @"lineTranslation should round-trip constant values."); - - MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.lineTranslation = functionStyleValue; - - mbgl::style::IntervalStops<std::array<float, 2>> intervalStops = { {{18, { 1, 1 }}} }; + @"Setting lineTranslation to a constant value expression should update line-translate."); + XCTAssertEqualObjects(layer.lineTranslation, constantExpression, + @"lineTranslation should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"{1, 1}"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.lineTranslation = functionExpression; + + mbgl::style::IntervalStops<std::array<float, 2>> intervalStops = {{ + { -INFINITY, { 1, 1 } }, + { 18, { 1, 1 } }, + }}; propertyValue = mbgl::style::CameraFunction<std::array<float, 2>> { intervalStops }; XCTAssertEqual(rawLayer->getLineTranslate(), propertyValue, - @"Setting lineTranslation to a camera function should update line-translate."); - XCTAssertEqualObjects(layer.lineTranslation, functionStyleValue, - @"lineTranslation should round-trip camera functions."); + @"Setting lineTranslation to a camera expression should update line-translate."); + XCTAssertEqualObjects(layer.lineTranslation, functionExpression, + @"lineTranslation should round-trip camera expressions."); layer.lineTranslation = nil; XCTAssertTrue(rawLayer->getLineTranslate().isUndefined(), @"Unsetting lineTranslation should return line-translate to the default value."); - XCTAssertEqualObjects(layer.lineTranslation, defaultStyleValue, + XCTAssertEqualObjects(layer.lineTranslation, defaultExpression, @"lineTranslation should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.lineTranslation = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.lineTranslation = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.lineTranslation = functionExpression, NSException, NSInvalidArgumentException, @"MGLLineLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.lineTranslation = functionExpression, NSException, NSInvalidArgumentException, @"MGLLineLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // line-translate-anchor { XCTAssertTrue(rawLayer->getLineTranslateAnchor().isUndefined(), @"line-translate-anchor should be unset initially."); - MGLStyleValue<NSValue *> *defaultStyleValue = layer.lineTranslationAnchor; + NSExpression *defaultExpression = layer.lineTranslationAnchor; - MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue:[NSValue valueWithMGLLineTranslationAnchor:MGLLineTranslationAnchorViewport]]; - layer.lineTranslationAnchor = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"'viewport'"]; + layer.lineTranslationAnchor = constantExpression; mbgl::style::PropertyValue<mbgl::style::TranslateAnchorType> propertyValue = { mbgl::style::TranslateAnchorType::Viewport }; XCTAssertEqual(rawLayer->getLineTranslateAnchor(), propertyValue, - @"Setting lineTranslationAnchor to a constant value should update line-translate-anchor."); - XCTAssertEqualObjects(layer.lineTranslationAnchor, constantStyleValue, - @"lineTranslationAnchor should round-trip constant values."); - - MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.lineTranslationAnchor = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::style::TranslateAnchorType> intervalStops = { {{18, mbgl::style::TranslateAnchorType::Viewport}} }; + @"Setting lineTranslationAnchor to a constant value expression should update line-translate-anchor."); + XCTAssertEqualObjects(layer.lineTranslationAnchor, constantExpression, + @"lineTranslationAnchor should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"'viewport'"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.lineTranslationAnchor = functionExpression; + + mbgl::style::IntervalStops<mbgl::style::TranslateAnchorType> intervalStops = {{ + { -INFINITY, mbgl::style::TranslateAnchorType::Viewport }, + { 18, mbgl::style::TranslateAnchorType::Viewport }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::style::TranslateAnchorType> { intervalStops }; XCTAssertEqual(rawLayer->getLineTranslateAnchor(), propertyValue, - @"Setting lineTranslationAnchor to a camera function should update line-translate-anchor."); - XCTAssertEqualObjects(layer.lineTranslationAnchor, functionStyleValue, - @"lineTranslationAnchor should round-trip camera functions."); + @"Setting lineTranslationAnchor to a camera expression should update line-translate-anchor."); + XCTAssertEqualObjects(layer.lineTranslationAnchor, functionExpression, + @"lineTranslationAnchor should round-trip camera expressions."); layer.lineTranslationAnchor = nil; XCTAssertTrue(rawLayer->getLineTranslateAnchor().isUndefined(), @"Unsetting lineTranslationAnchor should return line-translate-anchor to the default value."); - XCTAssertEqualObjects(layer.lineTranslationAnchor, defaultStyleValue, + XCTAssertEqualObjects(layer.lineTranslationAnchor, defaultExpression, @"lineTranslationAnchor should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.lineTranslationAnchor = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.lineTranslationAnchor = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.lineTranslationAnchor = functionExpression, NSException, NSInvalidArgumentException, @"MGLLineLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.lineTranslationAnchor = functionExpression, NSException, NSInvalidArgumentException, @"MGLLineLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // line-width { XCTAssertTrue(rawLayer->getLineWidth().isUndefined(), @"line-width should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.lineWidth; + NSExpression *defaultExpression = layer.lineWidth; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.lineWidth = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.lineWidth = constantExpression; mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getLineWidth(), propertyValue, - @"Setting lineWidth to a constant value should update line-width."); - XCTAssertEqualObjects(layer.lineWidth, constantStyleValue, - @"lineWidth should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.lineWidth = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting lineWidth to a constant value expression should update line-width."); + XCTAssertEqualObjects(layer.lineWidth, constantExpression, + @"lineWidth should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.lineWidth = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getLineWidth(), propertyValue, - @"Setting lineWidth to a camera function should update line-width."); - XCTAssertEqualObjects(layer.lineWidth, functionStyleValue, - @"lineWidth should round-trip camera functions."); + @"Setting lineWidth to a camera expression should update line-width."); + XCTAssertEqualObjects(layer.lineWidth, functionExpression, + @"lineWidth should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.lineWidth = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@18: constantExpression}]; + layer.lineWidth = functionExpression; mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getLineWidth(), propertyValue, - @"Setting lineWidth to a source function should update line-width."); - XCTAssertEqualObjects(layer.lineWidth, functionStyleValue, - @"lineWidth should round-trip source functions."); + @"Setting lineWidth to a data expression should update line-width."); + XCTAssertEqualObjects(layer.lineWidth, functionExpression, + @"lineWidth should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.lineWidth = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + layer.lineWidth = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -745,15 +808,15 @@ propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getLineWidth(), propertyValue, - @"Setting lineWidth to a composite function should update line-width."); - XCTAssertEqualObjects(layer.lineWidth, functionStyleValue, - @"lineWidth should round-trip composite functions."); + @"Setting lineWidth to a camera-data expression should update line-width."); + XCTAssertEqualObjects(layer.lineWidth, functionExpression, + @"lineWidth should round-trip camera-data expressions."); layer.lineWidth = nil; XCTAssertTrue(rawLayer->getLineWidth().isUndefined(), @"Unsetting lineWidth should return line-width to the default value."); - XCTAssertEqualObjects(layer.lineWidth, defaultStyleValue, + XCTAssertEqualObjects(layer.lineWidth, defaultExpression, @"lineWidth should return the default value after being unset."); // Transition property test layer.lineWidthTransition = transitionTest; diff --git a/platform/darwin/test/MGLPredicateTests.mm b/platform/darwin/test/MGLPredicateTests.mm index 6e6951dcdd..d8cad0b166 100644 --- a/platform/darwin/test/MGLPredicateTests.mm +++ b/platform/darwin/test/MGLPredicateTests.mm @@ -571,4 +571,11 @@ namespace mbgl { } } +- (void)testComparisonExpressionArray { + { + NSArray *expected = @[@"==", @1, @2]; + XCTAssertEqualObjects([NSPredicate predicateWithFormat:@"1 = 2"].mgl_jsonExpressionObject, expected); + } +} + @end diff --git a/platform/darwin/test/MGLRasterStyleLayerTests.mm b/platform/darwin/test/MGLRasterStyleLayerTests.mm index 26526e3a6c..5ded23ee3c 100644 --- a/platform/darwin/test/MGLRasterStyleLayerTests.mm +++ b/platform/darwin/test/MGLRasterStyleLayerTests.mm @@ -34,117 +34,132 @@ { XCTAssertTrue(rawLayer->getRasterBrightnessMax().isUndefined(), @"raster-brightness-max should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.maximumRasterBrightness; + NSExpression *defaultExpression = layer.maximumRasterBrightness; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.maximumRasterBrightness = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.maximumRasterBrightness = constantExpression; mbgl::style::PropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getRasterBrightnessMax(), propertyValue, - @"Setting maximumRasterBrightness to a constant value should update raster-brightness-max."); - XCTAssertEqualObjects(layer.maximumRasterBrightness, constantStyleValue, - @"maximumRasterBrightness should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.maximumRasterBrightness = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting maximumRasterBrightness to a constant value expression should update raster-brightness-max."); + XCTAssertEqualObjects(layer.maximumRasterBrightness, constantExpression, + @"maximumRasterBrightness should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.maximumRasterBrightness = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getRasterBrightnessMax(), propertyValue, - @"Setting maximumRasterBrightness to a camera function should update raster-brightness-max."); - XCTAssertEqualObjects(layer.maximumRasterBrightness, functionStyleValue, - @"maximumRasterBrightness should round-trip camera functions."); + @"Setting maximumRasterBrightness to a camera expression should update raster-brightness-max."); + XCTAssertEqualObjects(layer.maximumRasterBrightness, functionExpression, + @"maximumRasterBrightness should round-trip camera expressions."); layer.maximumRasterBrightness = nil; XCTAssertTrue(rawLayer->getRasterBrightnessMax().isUndefined(), @"Unsetting maximumRasterBrightness should return raster-brightness-max to the default value."); - XCTAssertEqualObjects(layer.maximumRasterBrightness, defaultStyleValue, + XCTAssertEqualObjects(layer.maximumRasterBrightness, defaultExpression, @"maximumRasterBrightness should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.maximumRasterBrightness = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.maximumRasterBrightness = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.maximumRasterBrightness = functionExpression, NSException, NSInvalidArgumentException, @"MGLRasterLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.maximumRasterBrightness = functionExpression, NSException, NSInvalidArgumentException, @"MGLRasterLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // raster-brightness-min { XCTAssertTrue(rawLayer->getRasterBrightnessMin().isUndefined(), @"raster-brightness-min should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.minimumRasterBrightness; + NSExpression *defaultExpression = layer.minimumRasterBrightness; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.minimumRasterBrightness = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.minimumRasterBrightness = constantExpression; mbgl::style::PropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getRasterBrightnessMin(), propertyValue, - @"Setting minimumRasterBrightness to a constant value should update raster-brightness-min."); - XCTAssertEqualObjects(layer.minimumRasterBrightness, constantStyleValue, - @"minimumRasterBrightness should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.minimumRasterBrightness = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting minimumRasterBrightness to a constant value expression should update raster-brightness-min."); + XCTAssertEqualObjects(layer.minimumRasterBrightness, constantExpression, + @"minimumRasterBrightness should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.minimumRasterBrightness = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getRasterBrightnessMin(), propertyValue, - @"Setting minimumRasterBrightness to a camera function should update raster-brightness-min."); - XCTAssertEqualObjects(layer.minimumRasterBrightness, functionStyleValue, - @"minimumRasterBrightness should round-trip camera functions."); + @"Setting minimumRasterBrightness to a camera expression should update raster-brightness-min."); + XCTAssertEqualObjects(layer.minimumRasterBrightness, functionExpression, + @"minimumRasterBrightness should round-trip camera expressions."); layer.minimumRasterBrightness = nil; XCTAssertTrue(rawLayer->getRasterBrightnessMin().isUndefined(), @"Unsetting minimumRasterBrightness should return raster-brightness-min to the default value."); - XCTAssertEqualObjects(layer.minimumRasterBrightness, defaultStyleValue, + XCTAssertEqualObjects(layer.minimumRasterBrightness, defaultExpression, @"minimumRasterBrightness should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.minimumRasterBrightness = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.minimumRasterBrightness = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.minimumRasterBrightness = functionExpression, NSException, NSInvalidArgumentException, @"MGLRasterLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.minimumRasterBrightness = functionExpression, NSException, NSInvalidArgumentException, @"MGLRasterLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // raster-contrast { XCTAssertTrue(rawLayer->getRasterContrast().isUndefined(), @"raster-contrast should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.rasterContrast; + NSExpression *defaultExpression = layer.rasterContrast; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.rasterContrast = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.rasterContrast = constantExpression; mbgl::style::PropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getRasterContrast(), propertyValue, - @"Setting rasterContrast to a constant value should update raster-contrast."); - XCTAssertEqualObjects(layer.rasterContrast, constantStyleValue, - @"rasterContrast should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.rasterContrast = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting rasterContrast to a constant value expression should update raster-contrast."); + XCTAssertEqualObjects(layer.rasterContrast, constantExpression, + @"rasterContrast should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.rasterContrast = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getRasterContrast(), propertyValue, - @"Setting rasterContrast to a camera function should update raster-contrast."); - XCTAssertEqualObjects(layer.rasterContrast, functionStyleValue, - @"rasterContrast should round-trip camera functions."); + @"Setting rasterContrast to a camera expression should update raster-contrast."); + XCTAssertEqualObjects(layer.rasterContrast, functionExpression, + @"rasterContrast should round-trip camera expressions."); layer.rasterContrast = nil; XCTAssertTrue(rawLayer->getRasterContrast().isUndefined(), @"Unsetting rasterContrast should return raster-contrast to the default value."); - XCTAssertEqualObjects(layer.rasterContrast, defaultStyleValue, + XCTAssertEqualObjects(layer.rasterContrast, defaultExpression, @"rasterContrast should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.rasterContrast = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.rasterContrast = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.rasterContrast = functionExpression, NSException, NSInvalidArgumentException, @"MGLRasterLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.rasterContrast = functionExpression, NSException, NSInvalidArgumentException, @"MGLRasterLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); // Transition property test layer.rasterContrastTransition = transitionTest; auto toptions = rawLayer->getRasterContrastTransition(); @@ -160,117 +175,132 @@ { XCTAssertTrue(rawLayer->getRasterFadeDuration().isUndefined(), @"raster-fade-duration should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.rasterFadeDuration; + NSExpression *defaultExpression = layer.rasterFadeDuration; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.rasterFadeDuration = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.rasterFadeDuration = constantExpression; mbgl::style::PropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getRasterFadeDuration(), propertyValue, - @"Setting rasterFadeDuration to a constant value should update raster-fade-duration."); - XCTAssertEqualObjects(layer.rasterFadeDuration, constantStyleValue, - @"rasterFadeDuration should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.rasterFadeDuration = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting rasterFadeDuration to a constant value expression should update raster-fade-duration."); + XCTAssertEqualObjects(layer.rasterFadeDuration, constantExpression, + @"rasterFadeDuration should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.rasterFadeDuration = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getRasterFadeDuration(), propertyValue, - @"Setting rasterFadeDuration to a camera function should update raster-fade-duration."); - XCTAssertEqualObjects(layer.rasterFadeDuration, functionStyleValue, - @"rasterFadeDuration should round-trip camera functions."); + @"Setting rasterFadeDuration to a camera expression should update raster-fade-duration."); + XCTAssertEqualObjects(layer.rasterFadeDuration, functionExpression, + @"rasterFadeDuration should round-trip camera expressions."); layer.rasterFadeDuration = nil; XCTAssertTrue(rawLayer->getRasterFadeDuration().isUndefined(), @"Unsetting rasterFadeDuration should return raster-fade-duration to the default value."); - XCTAssertEqualObjects(layer.rasterFadeDuration, defaultStyleValue, + XCTAssertEqualObjects(layer.rasterFadeDuration, defaultExpression, @"rasterFadeDuration should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.rasterFadeDuration = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.rasterFadeDuration = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.rasterFadeDuration = functionExpression, NSException, NSInvalidArgumentException, @"MGLRasterLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.rasterFadeDuration = functionExpression, NSException, NSInvalidArgumentException, @"MGLRasterLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // raster-hue-rotate { XCTAssertTrue(rawLayer->getRasterHueRotate().isUndefined(), @"raster-hue-rotate should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.rasterHueRotation; + NSExpression *defaultExpression = layer.rasterHueRotation; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.rasterHueRotation = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.rasterHueRotation = constantExpression; mbgl::style::PropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getRasterHueRotate(), propertyValue, - @"Setting rasterHueRotation to a constant value should update raster-hue-rotate."); - XCTAssertEqualObjects(layer.rasterHueRotation, constantStyleValue, - @"rasterHueRotation should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.rasterHueRotation = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting rasterHueRotation to a constant value expression should update raster-hue-rotate."); + XCTAssertEqualObjects(layer.rasterHueRotation, constantExpression, + @"rasterHueRotation should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.rasterHueRotation = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getRasterHueRotate(), propertyValue, - @"Setting rasterHueRotation to a camera function should update raster-hue-rotate."); - XCTAssertEqualObjects(layer.rasterHueRotation, functionStyleValue, - @"rasterHueRotation should round-trip camera functions."); + @"Setting rasterHueRotation to a camera expression should update raster-hue-rotate."); + XCTAssertEqualObjects(layer.rasterHueRotation, functionExpression, + @"rasterHueRotation should round-trip camera expressions."); layer.rasterHueRotation = nil; XCTAssertTrue(rawLayer->getRasterHueRotate().isUndefined(), @"Unsetting rasterHueRotation should return raster-hue-rotate to the default value."); - XCTAssertEqualObjects(layer.rasterHueRotation, defaultStyleValue, + XCTAssertEqualObjects(layer.rasterHueRotation, defaultExpression, @"rasterHueRotation should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.rasterHueRotation = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.rasterHueRotation = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.rasterHueRotation = functionExpression, NSException, NSInvalidArgumentException, @"MGLRasterLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.rasterHueRotation = functionExpression, NSException, NSInvalidArgumentException, @"MGLRasterLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // raster-opacity { XCTAssertTrue(rawLayer->getRasterOpacity().isUndefined(), @"raster-opacity should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.rasterOpacity; + NSExpression *defaultExpression = layer.rasterOpacity; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.rasterOpacity = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.rasterOpacity = constantExpression; mbgl::style::PropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getRasterOpacity(), propertyValue, - @"Setting rasterOpacity to a constant value should update raster-opacity."); - XCTAssertEqualObjects(layer.rasterOpacity, constantStyleValue, - @"rasterOpacity should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.rasterOpacity = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting rasterOpacity to a constant value expression should update raster-opacity."); + XCTAssertEqualObjects(layer.rasterOpacity, constantExpression, + @"rasterOpacity should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.rasterOpacity = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getRasterOpacity(), propertyValue, - @"Setting rasterOpacity to a camera function should update raster-opacity."); - XCTAssertEqualObjects(layer.rasterOpacity, functionStyleValue, - @"rasterOpacity should round-trip camera functions."); + @"Setting rasterOpacity to a camera expression should update raster-opacity."); + XCTAssertEqualObjects(layer.rasterOpacity, functionExpression, + @"rasterOpacity should round-trip camera expressions."); layer.rasterOpacity = nil; XCTAssertTrue(rawLayer->getRasterOpacity().isUndefined(), @"Unsetting rasterOpacity should return raster-opacity to the default value."); - XCTAssertEqualObjects(layer.rasterOpacity, defaultStyleValue, + XCTAssertEqualObjects(layer.rasterOpacity, defaultExpression, @"rasterOpacity should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.rasterOpacity = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.rasterOpacity = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.rasterOpacity = functionExpression, NSException, NSInvalidArgumentException, @"MGLRasterLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.rasterOpacity = functionExpression, NSException, NSInvalidArgumentException, @"MGLRasterLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); // Transition property test layer.rasterOpacityTransition = transitionTest; auto toptions = rawLayer->getRasterOpacityTransition(); @@ -286,39 +316,44 @@ { XCTAssertTrue(rawLayer->getRasterSaturation().isUndefined(), @"raster-saturation should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.rasterSaturation; + NSExpression *defaultExpression = layer.rasterSaturation; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.rasterSaturation = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.rasterSaturation = constantExpression; mbgl::style::PropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getRasterSaturation(), propertyValue, - @"Setting rasterSaturation to a constant value should update raster-saturation."); - XCTAssertEqualObjects(layer.rasterSaturation, constantStyleValue, - @"rasterSaturation should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.rasterSaturation = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting rasterSaturation to a constant value expression should update raster-saturation."); + XCTAssertEqualObjects(layer.rasterSaturation, constantExpression, + @"rasterSaturation should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.rasterSaturation = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getRasterSaturation(), propertyValue, - @"Setting rasterSaturation to a camera function should update raster-saturation."); - XCTAssertEqualObjects(layer.rasterSaturation, functionStyleValue, - @"rasterSaturation should round-trip camera functions."); + @"Setting rasterSaturation to a camera expression should update raster-saturation."); + XCTAssertEqualObjects(layer.rasterSaturation, functionExpression, + @"rasterSaturation should round-trip camera expressions."); layer.rasterSaturation = nil; XCTAssertTrue(rawLayer->getRasterSaturation().isUndefined(), @"Unsetting rasterSaturation should return raster-saturation to the default value."); - XCTAssertEqualObjects(layer.rasterSaturation, defaultStyleValue, + XCTAssertEqualObjects(layer.rasterSaturation, defaultExpression, @"rasterSaturation should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.rasterSaturation = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.rasterSaturation = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.rasterSaturation = functionExpression, NSException, NSInvalidArgumentException, @"MGLRasterLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.rasterSaturation = functionExpression, NSException, NSInvalidArgumentException, @"MGLRasterLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); // Transition property test layer.rasterSaturationTransition = transitionTest; auto toptions = rawLayer->getRasterSaturationTransition(); diff --git a/platform/darwin/test/MGLSDKTestHelpers.swift b/platform/darwin/test/MGLSDKTestHelpers.swift index f21041782e..576e3da86b 100644 --- a/platform/darwin/test/MGLSDKTestHelpers.swift +++ b/platform/darwin/test/MGLSDKTestHelpers.swift @@ -1,3 +1,4 @@ +import XCTest import Foundation class MGLSDKTestHelpers { diff --git a/platform/darwin/test/MGLShapeSourceTests.mm b/platform/darwin/test/MGLShapeSourceTests.mm index 60500959b6..868dca730a 100644 --- a/platform/darwin/test/MGLShapeSourceTests.mm +++ b/platform/darwin/test/MGLShapeSourceTests.mm @@ -298,7 +298,10 @@ // when a shape is included in the features array MGLPolygon *polygon = [MGLPolygon polygonWithCoordinates:coordinates count:sizeof(coordinates)/sizeof(coordinates[0]) interiorPolygons:nil]; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wobjc-literal-conversion" XCTAssertThrowsSpecificNamed([[MGLShapeSource alloc] initWithIdentifier:@"source-id-invalid" features:@[polygon] options:nil], NSException, NSInvalidArgumentException, @"Shape source should raise an exception if a shape is sent to the features initializer"); +#pragma clang diagnostic pop } - (void)testMGLShapeSourceWithShapesConvenienceInitializer { diff --git a/platform/darwin/test/MGLStyleLayerTests.h b/platform/darwin/test/MGLStyleLayerTests.h index f0b889f022..28c316c8ba 100644 --- a/platform/darwin/test/MGLStyleLayerTests.h +++ b/platform/darwin/test/MGLStyleLayerTests.h @@ -1,6 +1,9 @@ #import <Mapbox/Mapbox.h> #import <XCTest/XCTest.h> +#define MGLConstantExpression(constant) \ + [NSExpression expressionForConstantValue:constant] + @interface MGLStyleLayerTests : XCTestCase <MGLMapViewDelegate> @property (nonatomic, copy, readonly, class) NSString *layerType; diff --git a/platform/darwin/test/MGLStyleLayerTests.mm.ejs b/platform/darwin/test/MGLStyleLayerTests.mm.ejs index 5fdfc3d44e..0487066255 100644 --- a/platform/darwin/test/MGLStyleLayerTests.mm.ejs +++ b/platform/darwin/test/MGLStyleLayerTests.mm.ejs @@ -64,45 +64,49 @@ { XCTAssertTrue(rawLayer->get<%- camelize(originalPropertyName(property)) %>().isUndefined(), @"<%- originalPropertyName(property) %> should be unset initially."); - MGLStyleValue<<%- propertyType(property) %>> *defaultStyleValue = layer.<%- objCName(property) %>; + NSExpression *defaultExpression = layer.<%- objCName(property) %>; - MGLStyleValue<<%- propertyType(property) %>> *constantStyleValue = [MGLStyleValue<<%- propertyType(property) %>> valueWithRawValue:<%- objCTestValue(property, type, 3) %>]; - layer.<%- objCName(property) %> = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:<%- objCTestValue(property, type, true, 3) %>]; + layer.<%- objCName(property) %> = constantExpression; <% if (property["property-function"]) { -%> mbgl::style::DataDrivenPropertyValue<<%- mbglType(property) %>> propertyValue = { <%- mbglTestValue(property, type) %> }; <% } else { -%> mbgl::style::PropertyValue<<%- mbglType(property) %>> propertyValue = { <%- mbglTestValue(property, type) %> }; <% } -%> XCTAssertEqual(rawLayer->get<%- camelize(originalPropertyName(property)) %>(), propertyValue, - @"Setting <%- objCName(property) %> to a constant value should update <%- originalPropertyName(property) %>."); - XCTAssertEqualObjects(layer.<%- objCName(property) %>, constantStyleValue, - @"<%- objCName(property) %> should round-trip constant values."); - - MGLStyleValue<<%- propertyType(property) %>> * functionStyleValue = [MGLStyleValue<<%- propertyType(property) %>> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.<%- objCName(property) %> = functionStyleValue; - - mbgl::style::IntervalStops<<%- mbglType(property) %>> intervalStops = { {{18, <%- mbglTestValue(property, type) %>}} }; + @"Setting <%- objCName(property) %> to a constant value expression should update <%- originalPropertyName(property) %>."); + XCTAssertEqualObjects(layer.<%- objCName(property) %>, constantExpression, + @"<%- objCName(property) %> should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:<%- objCTestValue(property, type, false, 3) %>]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.<%- objCName(property) %> = functionExpression; + + mbgl::style::IntervalStops<<%- mbglType(property) %>> intervalStops = {{ + { -INFINITY, <%- mbglTestValue(property, type) %> }, + { 18, <%- mbglTestValue(property, type) %> }, + }}; propertyValue = mbgl::style::CameraFunction<<%- mbglType(property) %>> { intervalStops }; XCTAssertEqual(rawLayer->get<%- camelize(originalPropertyName(property)) %>(), propertyValue, - @"Setting <%- objCName(property) %> to a camera function should update <%- originalPropertyName(property) %>."); - XCTAssertEqualObjects(layer.<%- objCName(property) %>, functionStyleValue, - @"<%- objCName(property) %> should round-trip camera functions."); + @"Setting <%- objCName(property) %> to a camera expression should update <%- originalPropertyName(property) %>."); + XCTAssertEqualObjects(layer.<%- objCName(property) %>, functionExpression, + @"<%- objCName(property) %> should round-trip camera expressions."); <% if (property["property-function"] && isInterpolatable(property)) { -%> - functionStyleValue = [MGLStyleValue<<%- propertyType(property) %>> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.<%- objCName(property) %> = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@18: constantExpression}]; + layer.<%- objCName(property) %> = functionExpression; mbgl::style::ExponentialStops<<%- mbglType(property) %>> exponentialStops = { {{18, <%- mbglTestValue(property, type) %>}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<<%- mbglType(property) %>> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->get<%- camelize(originalPropertyName(property)) %>(), propertyValue, - @"Setting <%- objCName(property) %> to a source function should update <%- originalPropertyName(property) %>."); - XCTAssertEqualObjects(layer.<%- objCName(property) %>, functionStyleValue, - @"<%- objCName(property) %> should round-trip source functions."); + @"Setting <%- objCName(property) %> to a data expression should update <%- originalPropertyName(property) %>."); + XCTAssertEqualObjects(layer.<%- objCName(property) %>, functionExpression, + @"<%- objCName(property) %> should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<<%- propertyType(property) %>> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.<%- objCName(property) %> = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + layer.<%- objCName(property) %> = functionExpression; std::map<float, <%- mbglType(property) %>> innerStops { {18, <%- mbglTestValue(property, type) %>} }; mbgl::style::CompositeExponentialStops<<%- mbglType(property) %>> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -110,24 +114,25 @@ propertyValue = mbgl::style::CompositeFunction<<%- mbglType(property) %>> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->get<%- camelize(originalPropertyName(property)) %>(), propertyValue, - @"Setting <%- objCName(property) %> to a composite function should update <%- originalPropertyName(property) %>."); - XCTAssertEqualObjects(layer.<%- objCName(property) %>, functionStyleValue, - @"<%- objCName(property) %> should round-trip composite functions."); + @"Setting <%- objCName(property) %> to a camera-data expression should update <%- originalPropertyName(property) %>."); + XCTAssertEqualObjects(layer.<%- objCName(property) %>, functionExpression, + @"<%- objCName(property) %> should round-trip camera-data expressions."); <% } -%> <% if (!property.required) { -%> layer.<%- objCName(property) %> = nil; XCTAssertTrue(rawLayer->get<%- camelize(originalPropertyName(property)) %>().isUndefined(), @"Unsetting <%- objCName(property) %> should return <%- originalPropertyName(property) %> to the default value."); - XCTAssertEqualObjects(layer.<%- objCName(property) %>, defaultStyleValue, + XCTAssertEqualObjects(layer.<%- objCName(property) %>, defaultExpression, @"<%- objCName(property) %> should return the default value after being unset."); <% } -%> <% if (!property["property-function"]) { -%> - functionStyleValue = [MGLStyleValue<<%- propertyType(property) %>> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.<%- objCName(property) %> = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<<%- propertyType(property) %>> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.<%- objCName(property) %> = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.<%- objCName(property) %> = functionExpression, NSException, NSInvalidArgumentException, @"MGL<%- camelize(type) %>Layer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.<%- objCName(property) %> = functionExpression, NSException, NSInvalidArgumentException, @"MGL<%- camelize(type) %>Layer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); <% } -%> <% if (property["transition"] && !property.original) { -%> // Transition property test diff --git a/platform/darwin/test/MGLStyleValueTests.h b/platform/darwin/test/MGLStyleValueTests.h deleted file mode 100644 index a563de39f0..0000000000 --- a/platform/darwin/test/MGLStyleValueTests.h +++ /dev/null @@ -1,4 +0,0 @@ -#import <XCTest/XCTest.h> - -@interface MGLStyleValueTests : XCTestCase -@end diff --git a/platform/darwin/test/MGLStyleValueTests.m b/platform/darwin/test/MGLStyleValueTests.m deleted file mode 100644 index cd6eec8324..0000000000 --- a/platform/darwin/test/MGLStyleValueTests.m +++ /dev/null @@ -1,113 +0,0 @@ -#import <XCTest/XCTest.h> -#import <Mapbox/Mapbox.h> - -@interface MGLStyleValueTests : XCTestCase -@end - -@implementation MGLStyleValueTests - -- (void)testStoplessFunction { - XCTAssertThrowsSpecificNamed([MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential cameraStops:@{} options:nil], NSException, NSInvalidArgumentException, @"Stopless function should raise an exception"); -} - -- (void)testDeprecatedFunctions { - MGLShapeSource *shapeSource = [[MGLShapeSource alloc] initWithIdentifier:@"test" - shape:nil - options:nil]; - MGLSymbolStyleLayer *symbolStyleLayer = [[MGLSymbolStyleLayer alloc] initWithIdentifier:@"symbolLayer" - source:shapeSource]; - MGLCircleStyleLayer *circleStyleLayer = [[MGLCircleStyleLayer alloc] initWithIdentifier:@"circleLayer" - source:shapeSource]; - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - // deprecated function, stops with float values - NSDictionary<NSNumber *, MGLStyleValue<NSNumber *> *> *stops = @{ - @1: [MGLStyleValue<NSNumber *> valueWithRawValue:@0], - @2: [MGLStyleValue<NSNumber *> valueWithRawValue:@1], - @3: [MGLStyleValue<NSNumber *> valueWithRawValue:@2], - @4: [MGLStyleValue<NSNumber *> valueWithRawValue:@0], - }; - MGLStyleValue<NSNumber *> *iconHaloBlurStyleValue = - [MGLStyleValue<NSNumber *> valueWithInterpolationBase:1.0 stops:stops]; - symbolStyleLayer.iconHaloBlur = iconHaloBlurStyleValue; - XCTAssertEqualObjects(symbolStyleLayer.iconHaloBlur, iconHaloBlurStyleValue); - - // deprecated function, stops with boolean values - stops = @{ - @1: [MGLStyleValue<NSNumber *> valueWithRawValue:@YES], - @2: [MGLStyleValue<NSNumber *> valueWithRawValue:@NO], - @3: [MGLStyleValue<NSNumber *> valueWithRawValue:@YES], - @4: [MGLStyleValue<NSNumber *> valueWithRawValue:@NO], - }; - MGLStyleValue<NSNumber *> *iconAllowsOverlapStyleValue = - [MGLStyleValue<NSNumber *> valueWithInterpolationBase:1.0 stops:stops]; - symbolStyleLayer.iconAllowsOverlap = iconAllowsOverlapStyleValue; - // iconAllowsOverlap is boolean so mgl and mbgl conversions will coerce the developers stops into interval stops - MGLStyleValue<NSNumber *> *expectedIconAllowsOverlapStyleValue = - [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval - cameraStops:stops - options:nil]; - XCTAssertEqualObjects(symbolStyleLayer.iconAllowsOverlap, expectedIconAllowsOverlapStyleValue); - - /// - // creating and using MGLStyleFunctions directly - /// - - NSDictionary<NSNumber *, MGLStyleValue<NSNumber *> *> *circleRadiusStops = @{ - @0: [MGLStyleValue<NSNumber *> valueWithRawValue:@10], - @20: [MGLStyleValue<NSNumber *> valueWithRawValue:@5], - }; - MGLStyleFunction<NSNumber *> *circleRadiusFunction = - [MGLStyleFunction<NSNumber *> functionWithInterpolationBase:1.0 - stops:circleRadiusStops]; - circleStyleLayer.circleRadius = circleRadiusFunction; - MGLStyleValue<NSNumber *> *expectedCircleRadiusFunction = - [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential - cameraStops:circleRadiusStops - options:nil]; - // setting a data driven property to an MGLStyleFunction should return an exponential camera function - XCTAssertEqualObjects(circleStyleLayer.circleRadius, expectedCircleRadiusFunction); - - CGVector circleTranslationOne = CGVectorMake(100, 0); - CGVector circleTranslationTwo = CGVectorMake(0, 0); -#if TARGET_OS_IPHONE - NSValue *circleTranslationValueOne = [NSValue valueWithCGVector:circleTranslationOne]; - NSValue *circleTranslationValueTwo = [NSValue valueWithCGVector:circleTranslationTwo]; -#else - NSValue *circleTranslationValueOne = [NSValue value:&circleTranslationOne withObjCType:@encode(CGVector)]; - NSValue *circleTranslationValueTwo = [NSValue value:&circleTranslationTwo withObjCType:@encode(CGVector)]; -#endif - - NSDictionary<NSNumber *, MGLStyleValue<NSValue *> *> *circleTranslationStops = @{ - @0: [MGLStyleValue<NSValue *> valueWithRawValue:circleTranslationValueOne], - @10: [MGLStyleValue<NSValue *> valueWithRawValue:circleTranslationValueTwo], - }; - MGLStyleFunction<NSValue *> *circleTranslationFunction = - [MGLStyleFunction<NSValue *> functionWithInterpolationBase:1.0 - stops:circleTranslationStops]; - circleStyleLayer.circleTranslation = circleTranslationFunction; - MGLStyleValue<NSValue *> *expectedCircleTranslationFunction = - [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeExponential - cameraStops:circleTranslationStops - options:nil]; - // setting a non-data driven, interpolatable property to an MGLStyleFunction should return an exponential camera function - XCTAssertEqualObjects(circleStyleLayer.circleTranslation, expectedCircleTranslationFunction); - - NSDictionary<NSNumber *, MGLStyleValue<NSNumber *> *> *iconOptionalStops = @{ - @0: [MGLStyleValue<NSNumber *> valueWithRawValue:@NO], - @20: [MGLStyleValue<NSNumber *> valueWithRawValue:@YES], - }; - MGLStyleFunction<NSNumber *> *iconOptionalFunction = - [MGLStyleFunction<NSNumber *> valueWithInterpolationBase:1.0 - stops:iconOptionalStops]; - symbolStyleLayer.iconOptional = iconOptionalFunction; - MGLStyleValue<NSNumber *> *expectedIconOptionalFunction = - [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval - cameraStops:iconOptionalStops - options:nil]; - XCTAssertEqualObjects(symbolStyleLayer.iconOptional, expectedIconOptionalFunction); -#pragma clang diagnostic pop -} - -@end diff --git a/platform/darwin/test/MGLStyleValueTests.swift b/platform/darwin/test/MGLStyleValueTests.swift deleted file mode 100644 index c559037588..0000000000 --- a/platform/darwin/test/MGLStyleValueTests.swift +++ /dev/null @@ -1,362 +0,0 @@ -import XCTest -import Mapbox - -#if os(iOS) || os(watchOS) || os(tvOS) -typealias MGLColor = UIColor -#elseif os(macOS) -typealias MGLColor = NSColor -#endif - -#if swift(>=3.2) -#else -func XCTAssertEqual<T: FloatingPoint>(_ lhs: @autoclosure () throws -> T, _ rhs: @autoclosure () throws -> T, accuracy: T) { - XCTAssertEqualWithAccuracy(lhs, rhs, accuracy: accuracy) -} -#endif - -extension MGLStyleValueTests { - - struct Color { - var red: CGFloat = 0 - var green: CGFloat = 0 - var blue: CGFloat = 0 - var alpha: CGFloat = 0 - } - - func assertColorsEqualWithAccuracy(_ actual: MGLColor, _ expected: MGLColor, accuracy: Float = 1/255) { - var actualColor = Color() - var expectedColor = Color() - - actual.getRed(&actualColor.red, green: &actualColor.green, blue: &actualColor.blue, alpha: &actualColor.alpha) - expected.getRed(&expectedColor.red, green: &expectedColor.green, blue: &expectedColor.blue, alpha: &expectedColor.alpha) - - XCTAssertEqual(Float(actualColor.red), Float(expectedColor.red), accuracy: accuracy) - XCTAssertEqual(Float(actualColor.green), Float(expectedColor.green), accuracy: accuracy) - XCTAssertEqual(Float(actualColor.blue), Float(expectedColor.blue), accuracy: accuracy) - XCTAssertEqual(Float(actualColor.alpha), Float(expectedColor.alpha), accuracy: accuracy) - } - - func assertColorValuesEqual(_ actual: MGLStyleValue<MGLColor>, _ expected: MGLStyleValue<MGLColor>) { - guard type(of: actual) == type(of: expected) else { - XCTFail("Expected \(type(of: expected)), but found \(type(of: actual)) instead.") - return - } - - if let actualConstant = actual as? MGLConstantStyleValue<MGLColor> { - assertColorsEqualWithAccuracy(actualConstant.rawValue, (expected as! MGLConstantStyleValue<MGLColor>).rawValue) - } else if let actualFunction = actual as? MGLStyleFunction<MGLColor>, - let expectedFunction = expected as? MGLStyleFunction<MGLColor> { - - // unless we have stops, there's no need for a custom comparison - default to plain == assertion - guard let actualStops = actualFunction.stops, let expectedStops = expectedFunction.stops else { - XCTAssertEqual(actualFunction, expectedFunction) - return - } - - guard expectedStops is [String: Any] || expectedStops is [Float:Any] else { - XCTFail("Stop levels must be String or Float.") - return - } - - XCTAssertEqual(actualFunction.interpolationBase, expectedFunction.interpolationBase) - XCTAssertEqual(actualFunction.interpolationMode, expectedFunction.interpolationMode) - if let actualFunction = actualFunction as? MGLSourceStyleFunction<MGLColor>, - let expectedFunction = expectedFunction as? MGLSourceStyleFunction<MGLColor> { - XCTAssertEqual(actualFunction.defaultValue, expectedFunction.defaultValue) - } else if let actualFunction = actualFunction as? MGLCompositeStyleFunction<MGLColor>, - let expectedFunction = expectedFunction as? MGLCompositeStyleFunction<MGLColor> { - XCTAssertEqual(actualFunction.defaultValue, expectedFunction.defaultValue) - } - - func assertStopEqual (_ actualValue: Any?, _ expectedValue: Any?) { - guard type(of: actualValue) == type(of: expectedValue) else { - XCTFail("Expected stop value of type \(type(of: expectedValue)), but found \(type(of: actualValue)) instead.") - return - } - if let actualValue = actualValue as? MGLConstantStyleValue<MGLColor>, - let expectedValue = expectedValue as? MGLConstantStyleValue<MGLColor> { - assertColorsEqualWithAccuracy(actualValue.rawValue, expectedValue.rawValue) - } else if let actualValue = actualValue as? MGLConstantStyleValue<AnyObject>, - let expectedValue = expectedValue as? MGLConstantStyleValue<AnyObject> { - XCTAssertEqual(actualValue, expectedValue) - } else { - XCTFail("Unsupported stop value type \(type(of: actualValue)).") - } - } - - XCTAssertEqual(actualStops.count, expectedStops.count) - if let actualStops = actualStops as? [String:Any], let expectedStops = expectedStops as? [String: Any] { - for (key, value) in actualStops { - assertStopEqual(value, expectedStops[key]) - } - } else if let actualStops = actualStops as? [Float:Any], let expectedStops = expectedStops as? [Float:Any] { - for (key, value) in actualStops { - assertStopEqual(value, expectedStops[key]) - } - } else { - XCTFail("Expected stops of type \(type(of: Array(expectedStops.keys)).Index.self), but found \(type(of: Array(actualStops.keys)).Index.self) instead.") - return - } - } else { - XCTFail("MGLStyleValue<MGLColor> must be either a constant or a style function.") - } - } - - func testConstantValues() { - let shapeSource = MGLShapeSource(identifier: "source", shape: nil, options: nil) - let symbolStyleLayer = MGLSymbolStyleLayer(identifier: "symbolLayer", source: shapeSource) - let circleStyleLayer = MGLCircleStyleLayer(identifier: "circleLayer", source: shapeSource) - - // Boolean - symbolStyleLayer.iconAllowsOverlap = MGLConstantStyleValue(rawValue: true) - XCTAssertEqual((symbolStyleLayer.iconAllowsOverlap as! MGLConstantStyleValue<NSNumber>).rawValue, true) - - // Number - symbolStyleLayer.iconHaloWidth = MGLConstantStyleValue(rawValue: 3) - XCTAssertEqual((symbolStyleLayer.iconHaloWidth as! MGLConstantStyleValue<NSNumber>).rawValue, 3) - - // String - symbolStyleLayer.text = MGLConstantStyleValue(rawValue: "{name}") - XCTAssertEqual((symbolStyleLayer.text as! MGLConstantStyleValue<NSString>).rawValue, "{name}") - - var circleTranslationOne = CGVector(dx: 100, dy: 0) - let circleTranslationValueOne = NSValue(bytes: &circleTranslationOne, objCType: "{CGVector=dd}") - - // non-data-driven (interpolatable property value), set to constant style value - let expectedCircleTranslationValue = MGLStyleValue<NSValue>(rawValue: circleTranslationValueOne) - circleStyleLayer.circleTranslation = expectedCircleTranslationValue - XCTAssertEqual(circleStyleLayer.circleTranslation, expectedCircleTranslationValue) - - // non-data-driven (enumeration property value), set to constant style value - let expectedCircleScaleAlignmentValue = MGLStyleValue<NSValue>(rawValue: NSValue(mglCircleScaleAlignment: .map)) - circleStyleLayer.circleScaleAlignment = expectedCircleScaleAlignmentValue - XCTAssertEqual(circleStyleLayer.circleScaleAlignment, expectedCircleScaleAlignmentValue) - } - - func testFunctionsWithNonDataDrivenProperties() { - let shapeSource = MGLShapeSource(identifier: "test", shape: nil, options: nil) - let circleStyleLayer = MGLCircleStyleLayer(identifier: "circleLayer", source: shapeSource) - - var circleTranslationOne = CGVector(dx: 100, dy: 0) - let circleTranslationValueOne = NSValue(bytes: &circleTranslationOne, objCType: "{CGVector=dd}") - var circleTranslationTwo = CGVector(dx: 0, dy: 0) - let circleTranslationValueTwo = NSValue(bytes: &circleTranslationTwo, objCType: "{CGVector=dd}") - - let circleTranslationStops : [Float:MGLStyleValue<NSValue>] = [ - 0: MGLStyleValue<NSValue>(rawValue: circleTranslationValueOne), - 10: MGLStyleValue<NSValue>(rawValue: circleTranslationValueTwo) - ] - - // non-data-driven (interpolatable property value), camera function with CGVector (NSValue) stop values - let expectedCircleTranslationValue = MGLStyleValue<NSValue>( - interpolationMode: .interval, - cameraStops: circleTranslationStops, - options: nil - ) - circleStyleLayer.circleTranslation = expectedCircleTranslationValue - XCTAssertEqual(circleStyleLayer.circleTranslation, expectedCircleTranslationValue) - - // non-data-driven (enumeration property value), camera function with MGLCircleScaleAlignment enum (NSValue) stop values - let scaleAlignmentStops : [Float:MGLStyleValue<NSValue>] = [ - 0: MGLStyleValue(rawValue: NSValue(mglCircleScaleAlignment: .map)), - 10: MGLStyleValue(rawValue: NSValue(mglCircleScaleAlignment: .viewport)) - ] - let expectedCircleScaleAlignmentValue = MGLStyleValue<NSValue>( - interpolationMode: .interval, - cameraStops: scaleAlignmentStops, - options: nil - ) - circleStyleLayer.circleScaleAlignment = expectedCircleScaleAlignmentValue - XCTAssertEqual(circleStyleLayer.circleScaleAlignment, expectedCircleScaleAlignmentValue) - } - - func testFunctionsWithDataDrivenProperties() { - let shapeSource = MGLShapeSource(identifier: "test", shape: nil, options: nil) - let circleStyleLayer = MGLCircleStyleLayer(identifier: "circleLayer", source: shapeSource) - - // data-driven, camera function with exponential color stop values - let redGreenStops : [Float:MGLStyleValue<MGLColor>] = [ - 0: MGLStyleValue<MGLColor>(rawValue: .red), - 10: MGLStyleValue<MGLColor>(rawValue: .red), - 15: MGLStyleValue<MGLColor>(rawValue: .green) - ] - let expectedCircleColorValue = MGLStyleValue<MGLColor>( - interpolationMode: .exponential, - cameraStops: redGreenStops, - options: [.interpolationBase: 10.0] - ) - circleStyleLayer.circleColor = expectedCircleColorValue - assertColorValuesEqual(circleStyleLayer.circleColor as! MGLStyleFunction<MGLColor>, expectedCircleColorValue as! MGLStyleFunction<MGLColor>) - - // data-driven, source function with categorical color stop values with string attribute keys - let redOnlyStops = [ - "red": MGLStyleValue<MGLColor>(rawValue: .red) - ] - let expectedRedCategoricalValue = MGLStyleValue<MGLColor>( - interpolationMode: .categorical, - sourceStops: redOnlyStops, - attributeName: "red", - options: [.defaultValue: MGLStyleValue<MGLColor>(rawValue: .cyan)] - ) - circleStyleLayer.circleColor = expectedRedCategoricalValue - assertColorValuesEqual(circleStyleLayer.circleColor, expectedRedCategoricalValue) - - // data-driven, source function with categorical color stop values with integer attribute keys - let greenOrangeStops : [Float:MGLStyleValue<MGLColor>] = [ - 0: MGLStyleValue<MGLColor>(rawValue: .green), - 100: MGLStyleValue<MGLColor>(rawValue: .orange) - ] - let expectedGreenOrangeCategoricalValue = MGLStyleValue<MGLColor>( - interpolationMode: .categorical, - sourceStops: greenOrangeStops, - attributeName: "temp", - options: [.defaultValue: MGLStyleValue<MGLColor>(rawValue: .red)] - ) - circleStyleLayer.circleColor = expectedGreenOrangeCategoricalValue - assertColorValuesEqual(circleStyleLayer.circleColor, expectedGreenOrangeCategoricalValue) - - // data-driven, source function with exponential color stop values - let expectedRedGreenSourceExponentialValue = MGLStyleValue<MGLColor>( - interpolationMode: .exponential, - sourceStops: redGreenStops, - attributeName: "temp", - options: nil - ) - circleStyleLayer.circleColor = expectedRedGreenSourceExponentialValue - assertColorValuesEqual(circleStyleLayer.circleColor, expectedRedGreenSourceExponentialValue) - - // data-driven, identity source function - let expectedSourceIdentityValue = MGLStyleValue<MGLColor>( - interpolationMode: .identity, - sourceStops: nil, - attributeName: "size", - options: [.defaultValue: MGLStyleValue<MGLColor>(rawValue: .green)] - ) - circleStyleLayer.circleColor = expectedSourceIdentityValue - assertColorValuesEqual(circleStyleLayer.circleColor, expectedSourceIdentityValue) - - // data-driven, source function with categorical color stop values with boolean attribute keys - let booleanCategoricalStops = [ - false: MGLStyleValue<NSNumber>(rawValue: 0), - true: MGLStyleValue<NSNumber>(rawValue: 2) - ] - let expectedCircleBlurCategoricalValue = MGLStyleValue<NSNumber>( - interpolationMode: .categorical, - sourceStops: booleanCategoricalStops, - attributeName: "fuzzy", - options: [.defaultValue: MGLStyleValue<NSNumber>(rawValue: 42)] - ) - circleStyleLayer.circleBlur = expectedCircleBlurCategoricalValue - XCTAssertEqual(circleStyleLayer.circleBlur, expectedCircleBlurCategoricalValue) - - // data-driven, composite function with inner categorical color stop values with string attribute keys nested in outer camera stops - let smallRadius = MGLStyleValue<NSNumber>(rawValue: 5) - let mediumRadius = MGLStyleValue<NSNumber>(rawValue: 10) - let largeRadius = MGLStyleValue<NSNumber>(rawValue: 20) - let radiusCompositeCategoricalStops: [Float: [String: MGLStyleValue<NSNumber>]] = [ - 0: ["green": smallRadius], - 10: ["green": smallRadius], - 15: ["green": largeRadius], - 20: ["green": largeRadius] - ] - let defaultRadius = MGLStyleValue<NSNumber>(rawValue: 2) - let expectedCompositeCategoricalValue = MGLStyleValue<NSNumber>( - interpolationMode: .categorical, - compositeStops: radiusCompositeCategoricalStops, - attributeName: "color", - options: [.defaultValue: defaultRadius] - ) - circleStyleLayer.circleRadius = expectedCompositeCategoricalValue - - var compositeValue = circleStyleLayer.circleRadius as! MGLCompositeStyleFunction - var expectedCompositeValue = expectedCompositeCategoricalValue as! MGLCompositeStyleFunction - XCTAssertEqual(compositeValue.attributeName, expectedCompositeValue.attributeName) - XCTAssertEqual(compositeValue.stops as NSDictionary, radiusCompositeCategoricalStops as NSDictionary) - XCTAssertEqual(compositeValue.interpolationMode, expectedCompositeValue.interpolationMode) - XCTAssertEqual(compositeValue.defaultValue, expectedCompositeValue.defaultValue) - - // data-driven, composite function with inner exponential color stop values nested in outer camera stops - let radiusCompositeExponentialOrIntervalStops: [Float: [Float: MGLStyleValue<NSNumber>]] = [ - 0: [0: MGLStyleValue<NSNumber>(rawValue: 5)], - 10: [200: MGLStyleValue<NSNumber>(rawValue: 5)], - 20: [200: MGLStyleValue<NSNumber>(rawValue: 20)] - ] - - let expectedStops = [ - 0: [0: MGLStyleValue<NSNumber>(rawValue: 5)], - 10: [200: MGLStyleValue<NSNumber>(rawValue: 5)], - 20: [200: MGLStyleValue<NSNumber>(rawValue: 20)] - ] - circleStyleLayer.circleRadius = MGLStyleValue<NSNumber>( - interpolationMode: .exponential, - compositeStops: [ - 0: [0: MGLStyleValue<NSNumber>(rawValue: 5)], - 10: [200: MGLStyleValue<NSNumber>(rawValue: 5)], - 20: [200: MGLStyleValue<NSNumber>(rawValue: 20)] - ], - attributeName: "temp", - options: [.defaultValue: mediumRadius] - ) - - let expectedCompositeExponentialValue = MGLStyleValue<NSNumber>( - interpolationMode: .exponential, - compositeStops: radiusCompositeExponentialOrIntervalStops, - attributeName: "temp", - options: [.defaultValue: mediumRadius] - ) - - compositeValue = circleStyleLayer.circleRadius as! MGLCompositeStyleFunction - expectedCompositeValue = expectedCompositeExponentialValue as! MGLCompositeStyleFunction - XCTAssertEqual(compositeValue.attributeName, expectedCompositeValue.attributeName) - XCTAssertEqual(compositeValue.stops as NSDictionary, expectedStops as NSDictionary) - XCTAssertEqual(compositeValue.interpolationMode, expectedCompositeValue.interpolationMode) - XCTAssertEqual(compositeValue.defaultValue, expectedCompositeValue.defaultValue) - - // get a value back - if let returnedCircleRadius = circleStyleLayer.circleRadius as? MGLCompositeStyleFunction<NSNumber> { - if let returnedStops = returnedCircleRadius.stops as NSDictionary? as? [NSNumber: [NSNumber: MGLStyleValue<NSNumber>]] { - let lhs: MGLStyleValue<NSNumber> = returnedStops[0]!.values.first! - let rhs: MGLStyleValue<NSNumber> = radiusCompositeExponentialOrIntervalStops[0]!.values.first! - XCTAssertEqual(lhs, rhs) - } - } - - // get value back as base class - if let returnedCircleRadius = circleStyleLayer.circleRadius as? MGLStyleFunction<NSNumber> { - if let returnedStops = returnedCircleRadius.stops as NSDictionary? as? [NSNumber: [NSNumber: MGLStyleValue<NSNumber>]] { - let lhs: MGLStyleValue<NSNumber> = returnedStops[0]!.values.first! - let rhs: MGLStyleValue<NSNumber> = radiusCompositeExponentialOrIntervalStops[0]!.values.first! - XCTAssertEqual(lhs, rhs) - } - } - - // data-driven, composite function with inner interval color stop values nested in outer camera stops - let expectedCompositeIntervalValue = MGLStyleValue<NSNumber>( - interpolationMode: .interval, - compositeStops: [ - - 10: [200: MGLStyleValue<NSNumber>(rawValue: 5)], - 20: [200: MGLStyleValue<NSNumber>(rawValue: 20)] - ], - attributeName: "temp", - options: nil - ) - circleStyleLayer.circleRadius = MGLStyleValue<NSNumber>( - interpolationMode: .interval, - compositeStops: [ - 0: [0: MGLStyleValue<NSNumber>(rawValue: 5)], - 10: [200: MGLStyleValue<NSNumber>(rawValue: 5)], - 20: [200: MGLStyleValue<NSNumber>(rawValue: 20)] - ], - attributeName: "temp", - options: nil - ) - - compositeValue = circleStyleLayer.circleRadius as! MGLCompositeStyleFunction - expectedCompositeValue = expectedCompositeIntervalValue as! MGLCompositeStyleFunction - XCTAssertEqual(compositeValue.attributeName, expectedCompositeValue.attributeName) - XCTAssertEqual(compositeValue.stops as NSDictionary, expectedStops as NSDictionary) - XCTAssertEqual(compositeValue.interpolationMode, expectedCompositeValue.interpolationMode) - XCTAssertEqual(compositeValue.defaultValue, expectedCompositeValue.defaultValue) - } -} diff --git a/platform/darwin/test/MGLSymbolStyleLayerTests.mm b/platform/darwin/test/MGLSymbolStyleLayerTests.mm index 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."); } } diff --git a/platform/darwin/test/test-Bridging-Header.h b/platform/darwin/test/test-Bridging-Header.h index 5d23e9d6c5..1b2cb5d6d0 100644 --- a/platform/darwin/test/test-Bridging-Header.h +++ b/platform/darwin/test/test-Bridging-Header.h @@ -1,4 +1,4 @@ // // Use this file to import your target's public headers that you would like to expose to Swift. // -#import "MGLStyleValueTests.h" + |