Files
MapLibreParticleLab/platform/darwin/test/MGLDocumentationExampleTests.swift
Fabian Guerra Soto 1d7b243a06 [ios, macos] Support expressions in formatting parameters. (#14198)
Modified MGLAttributedExpression object to support NSExpressions as formatting attributes only. This aligns with GL and Android implementation and specification.

Modified the documentation accordingly, and changed MGLFontSizeAttribute to MGLFontScaleAttribute to clarify the true function of the formatting parameter.
2019-03-22 13:45:39 -07:00

566 lines
24 KiB
Swift

import XCTest
import Mapbox
#if os(iOS)
import UIKit
#else
import Cocoa
#endif
/**
Test cases that ensure the inline examples in the project documentation
compile.
To add an example:
1. Add a test case named in the form testMGLClass or testMGLClass$method.
2. Wrap the code you'd like to appear in the documentation within the
following comment blocks:
```
//#-example-code
...
//#-end-example-code
```
3. Insert an empty Swift code block inside the header file where you'd like the
example code to be inserted.
4. Run `make darwin-update-examples` to extract example code from the test
method below and insert it into the header.
*/
class MGLDocumentationExampleTests: XCTestCase, MGLMapViewDelegate {
var mapView: MGLMapView!
var styleLoadingExpectation: XCTestExpectation!
static let styleURL = Bundle(for: MGLDocumentationExampleTests.self).url(forResource: "one-liner", withExtension: "json")!
// Mock MGLOfflineStorage singleton so that it doesn't start long-running tasks that could interfere with other tests.
fileprivate class MGLOfflineStorageMock {
static let shared = MGLOfflineStorageMock()
func addPack(for: MGLOfflineRegion, withContext: Data, completionHandler: MGLOfflinePackAdditionCompletionHandler? = nil) {
XCTAssert(MGLOfflineStorage.shared.responds(to: #selector(MGLOfflineStorage.shared.addPack(for:withContext:completionHandler:))))
if let completionHandler = completionHandler {
completionHandler(nil, NSError(domain: "MGLDocumentationExampleError", code: 0, userInfo: [NSLocalizedDescriptionKey: "\(#function) is mocked and not functional."]))
}
}
}
override func setUp() {
super.setUp()
mapView = MGLMapView(frame: CGRect(x: 0, y: 0, width: 256, height: 256), styleURL: MGLDocumentationExampleTests.styleURL)
mapView.delegate = self
styleLoadingExpectation = expectation(description: "Map view should finish loading style")
waitForExpectations(timeout: 10, handler: nil)
}
override func tearDown() {
mapView = nil
styleLoadingExpectation = nil
super.tearDown()
}
func mapView(_ mapView: MGLMapView, didFinishLoading style: MGLStyle) {
styleLoadingExpectation.fulfill()
}
func testMGLLight() {
//#-example-code
let light = MGLLight()
let position = MGLSphericalPosition(radial: 5, azimuthal: 180, polar: 80)
light.position = NSExpression(forConstantValue: NSValue(mglSphericalPosition: position))
light.anchor = NSExpression(forConstantValue: "map")
mapView.style?.light = light
//#-end-example-code
XCTAssertNotNil(mapView.style?.light)
}
func testMGLTilePyramidOfflineRegion() {
class MGLStyle {
static var lightStyleURL: URL {
return MGLDocumentationExampleTests.styleURL
}
}
typealias MGLOfflineStorage = MGLOfflineStorageMock
//#-example-code
let northeast = CLLocationCoordinate2D(latitude: 40.989329, longitude: -102.062592)
let southwest = CLLocationCoordinate2D(latitude: 36.986207, longitude: -109.049896)
let bbox = MGLCoordinateBounds(sw: southwest, ne: northeast)
let region = MGLTilePyramidOfflineRegion(styleURL: MGLStyle.lightStyleURL, bounds: bbox, fromZoomLevel: 11, toZoomLevel: 14)
let context = "Tile Pyramid Region".data(using: .utf8)
MGLOfflineStorage.shared.addPack(for: region, withContext: context!)
//#-end-example-code
XCTAssertNotNil(region)
}
func testMGLShapeOfflineRegion() {
class MGLStyle {
static var lightStyleURL: URL {
return MGLDocumentationExampleTests.styleURL
}
}
typealias MGLOfflineStorage = MGLOfflineStorageMock
//#-example-code
var coordinates = [
CLLocationCoordinate2D(latitude: 45.522585, longitude: -122.685699),
CLLocationCoordinate2D(latitude: 45.534611, longitude: -122.708873),
CLLocationCoordinate2D(latitude: 45.530883, longitude: -122.678833)
]
let triangle = MGLPolygon(coordinates: &coordinates, count: UInt(coordinates.count))
let region = MGLShapeOfflineRegion(styleURL: MGLStyle.lightStyleURL, shape: triangle, fromZoomLevel: 11, toZoomLevel: 14)
let context = "Triangle Region".data(using: .utf8)
MGLOfflineStorage.shared.addPack(for: region, withContext: context!)
//#-end-example-code
XCTAssertNotNil(region)
}
func testMGLOfflinePack() {
typealias MGLOfflineStorage = MGLOfflineStorageMock
let northeast = CLLocationCoordinate2D(latitude: 40.989329, longitude: -102.062592)
let southwest = CLLocationCoordinate2D(latitude: 36.986207, longitude: -109.049896)
let bbox = MGLCoordinateBounds(sw: southwest, ne: northeast)
let region = MGLTilePyramidOfflineRegion(styleURL: MGLDocumentationExampleTests.styleURL, bounds: bbox, fromZoomLevel: 11, toZoomLevel: 14)
let context = "Tile Pyramid Region".data(using: .utf8)!
//#-example-code
MGLOfflineStorage.shared.addPack(for: region, withContext: context) { (pack, error) in
guard let pack = pack else {
// If adding the pack fails, log an error to console.
print("Error:", error?.localizedDescription ?? "unknown error adding pack at \(#file)(\(#line)) in \(#function)")
return
}
// Start an MGLOfflinePack download
pack.resume()
}
//#-end-example-code
}
func testMGLShape$shapeWithData_encoding_error_() {
let mainBundle = Bundle(for: MGLDocumentationExampleTests.self)
//#-example-code
let url = mainBundle.url(forResource: "amsterdam", withExtension: "geojson")!
let data = try! Data(contentsOf: url)
let feature = try! MGLShape(data: data, encoding: String.Encoding.utf8.rawValue) as! MGLShapeCollectionFeature
//#-end-example-code
XCTAssertNotNil(feature.shapes.first as? MGLPolygonFeature)
}
func testMGLShapeSource() {
//#-example-code
var coordinates: [CLLocationCoordinate2D] = [
CLLocationCoordinate2D(latitude: 37.77, longitude: -122.42),
CLLocationCoordinate2D(latitude: 38.91, longitude: -77.04),
]
let polyline = MGLPolylineFeature(coordinates: &coordinates, count: UInt(coordinates.count))
let source = MGLShapeSource(identifier: "lines", features: [polyline], options: nil)
mapView.style?.addSource(source)
//#-end-example-code
XCTAssertNotNil(mapView.style?.source(withIdentifier: "lines"))
}
func testMGLRasterTileSource() {
//#-example-code
let source = MGLRasterTileSource(identifier: "clouds", tileURLTemplates: ["https://example.com/raster-tiles/{z}/{x}/{y}.png"], options: [
.minimumZoomLevel: 9,
.maximumZoomLevel: 16,
.tileSize: 512,
.attributionInfos: [
MGLAttributionInfo(title: NSAttributedString(string: "© Mapbox"), url: URL(string: "https://mapbox.com"))
]
])
mapView.style?.addSource(source)
//#-end-example-code
XCTAssertNotNil(mapView.style?.source(withIdentifier: "clouds"))
}
func testMGLRasterDEMSource() {
// We want to use mapbox.terrain-rgb in the example, but using a mapbox:
// URL requires setting an access token. So this identically named
// subclass of MGLRasterDEMSource swaps in a nonexistent URL.
class MGLRasterDEMSource: Mapbox.MGLRasterDEMSource {
override init(identifier: String, configurationURL: URL, tileSize: CGFloat = 256) {
let bogusURL = URL(string: "https://example.com/raster-rgb.json")!
super.init(identifier: identifier, configurationURL: bogusURL, tileSize: tileSize)
}
}
//#-example-code
let terrainRGBURL = URL(string: "mapbox://mapbox.terrain-rgb")!
let source = MGLRasterDEMSource(identifier: "hills", configurationURL: terrainRGBURL)
mapView.style?.addSource(source)
//#-end-example-code
XCTAssertNotNil(mapView.style?.source(withIdentifier: "hills"))
}
func testMGLVectorTileSource() {
//#-example-code
let source = MGLVectorTileSource(identifier: "pois", tileURLTemplates: ["https://example.com/vector-tiles/{z}/{x}/{y}.mvt"], options: [
.minimumZoomLevel: 9,
.maximumZoomLevel: 16,
.attributionInfos: [
MGLAttributionInfo(title: NSAttributedString(string: "© Mapbox"), url: URL(string: "https://mapbox.com"))
]
])
mapView.style?.addSource(source)
//#-end-example-code
XCTAssertNotNil(mapView.style?.source(withIdentifier: "pois"))
}
func testMGLPolyline() {
//#-example-code
let coordinates = [
CLLocationCoordinate2D(latitude: 35.68476, longitude: -220.24257),
CLLocationCoordinate2D(latitude: 37.78428, longitude: -122.41310)
]
let polyline = MGLPolyline(coordinates: coordinates, count: UInt(coordinates.count))
//#-end-example-code
XCTAssertNotNil(polyline)
}
func testMGLImageSource() {
//#-example-code
let coordinates = MGLCoordinateQuad(
topLeft: CLLocationCoordinate2D(latitude: 46.437, longitude: -80.425),
bottomLeft: CLLocationCoordinate2D(latitude: 37.936, longitude: -80.425),
bottomRight: CLLocationCoordinate2D(latitude: 37.936, longitude: -71.516),
topRight: CLLocationCoordinate2D(latitude: 46.437, longitude: -71.516))
let source = MGLImageSource(identifier: "radar", coordinateQuad: coordinates, url: URL(string: "https://www.mapbox.com/mapbox-gl-js/assets/radar.gif")!)
mapView.style?.addSource(source)
//#-end-example-code
XCTAssertNotNil(mapView.style?.source(withIdentifier: "radar"))
}
func testMGLCircleStyleLayer() {
let population = MGLVectorTileSource(identifier: "population", configurationURL: URL(string: "https://example.com/style.json")!)
mapView.style?.addSource(population)
//#-example-code
let layer = MGLCircleStyleLayer(identifier: "circles", source: population)
layer.sourceLayerIdentifier = "population"
#if os(macOS)
layer.circleColor = NSExpression(forConstantValue: NSColor.green)
#else
layer.circleColor = NSExpression(forConstantValue: UIColor.green)
#endif
layer.circleRadius = NSExpression(format: "mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'exponential', 1.75, %@)",
[12: 2,
22: 180])
layer.circleOpacity = NSExpression(forConstantValue: 0.7)
layer.predicate = NSPredicate(format: "%K == %@", "marital-status", "married")
mapView.style?.addLayer(layer)
//#-end-example-code
XCTAssertNotNil(mapView.style?.layer(withIdentifier: "circles"))
}
func testMGLLineStyleLayer() {
let trails = MGLVectorTileSource(identifier: "trails", configurationURL: URL(string: "https://example.com/style.json")!)
mapView.style?.addSource(trails)
//#-example-code
let layer = MGLLineStyleLayer(identifier: "trails-path", source: trails)
layer.sourceLayerIdentifier = "trails"
layer.lineWidth = NSExpression(format: "mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'exponential', 1.5, %@)",
[14: 2,
18: 20])
#if os(macOS)
layer.lineColor = NSExpression(forConstantValue: NSColor.brown)
#else
layer.lineColor = NSExpression(forConstantValue: UIColor.brown)
#endif
layer.lineCap = NSExpression(forConstantValue: "round")
layer.predicate = NSPredicate(format: "%K == %@", "trail-type", "mountain-biking")
mapView.style?.addLayer(layer)
//#-end-example-code
XCTAssertNotNil(mapView.style?.layer(withIdentifier: "trails-path"))
}
func testMGLFillStyleLayer() {
let parks = MGLVectorTileSource(identifier: "parks", configurationURL: URL(string: "https://example.com/style.json")!)
mapView.style?.addSource(parks)
//#-example-code
let layer = MGLFillStyleLayer(identifier: "parks", source: parks)
layer.sourceLayerIdentifier = "parks"
#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
XCTAssertNotNil(mapView.style?.layer(withIdentifier: "parks"))
}
func testMGLFillExtrusionStyleLayer() {
let buildings = MGLVectorTileSource(identifier: "buildings", configurationURL: URL(string: "https://example.com/style.json")!)
mapView.style?.addSource(buildings)
//#-example-code
let layer = MGLFillExtrusionStyleLayer(identifier: "buildings", source: buildings)
layer.sourceLayerIdentifier = "building"
layer.fillExtrusionHeight = NSExpression(forKeyPath: "height")
layer.fillExtrusionBase = NSExpression(forKeyPath: "min_height")
layer.predicate = NSPredicate(format: "extrude == 'true'")
mapView.style?.addLayer(layer)
//#-end-example-code
XCTAssertNotNil(mapView.style?.layer(withIdentifier: "buildings"))
}
func testMGLHeatmapStyleLayer() {
let earthquakes = MGLShapeSource(identifier: "earthquakes", url: URL(string: "https://example.com/earthquakes.json")!, options: [:])
mapView.style?.addSource(earthquakes)
//#-example-code
let layer = MGLHeatmapStyleLayer(identifier: "earthquake-heat", source: earthquakes)
layer.heatmapWeight = NSExpression(format: "mgl_interpolate:withCurveType:parameters:stops:(magnitude, 'linear', nil, %@)",
[0: 0,
6: 1])
layer.heatmapIntensity = NSExpression(format: "mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)",
[0: 1,
9: 3])
mapView.style?.addLayer(layer)
//#-end-example-code
XCTAssertNotNil(mapView.style?.layer(withIdentifier: "earthquake-heat"))
}
func testMGLSymbolStyleLayer() {
let pois = MGLVectorTileSource(identifier: "pois", configurationURL: URL(string: "https://example.com/style.json")!)
mapView.style?.addSource(pois)
//#-example-code
let layer = MGLSymbolStyleLayer(identifier: "coffeeshops", source: pois)
layer.sourceLayerIdentifier = "pois"
layer.iconImageName = 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 = NSExpression(forConstantValue: NSValue(bytes: &vector, objCType: "{CGVector=dd}"))
#else
layer.textTranslation = NSExpression(forConstantValue: NSValue(cgVector: CGVector(dx: 10, dy: 0)))
#endif
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
XCTAssertNotNil(mapView.style?.layer(withIdentifier: "coffeeshops"))
}
func testMGLRasterStyleLayer() {
let source = MGLRasterTileSource(identifier: "clouds", tileURLTemplates: ["https://example.com/raster-tiles/{z}/{x}/{y}.png"], options: [
.minimumZoomLevel: 9,
.maximumZoomLevel: 16,
.tileSize: 512,
.attributionInfos: [
MGLAttributionInfo(title: NSAttributedString(string: "© Mapbox"), url: URL(string: "https://mapbox.com"))
]
])
mapView.style?.addSource(source)
//#-example-code
let layer = MGLRasterStyleLayer(identifier: "clouds", source: source)
layer.rasterOpacity = NSExpression(forConstantValue: 0.5)
mapView.style?.addLayer(layer)
//#-end-example-code
XCTAssertNotNil(mapView.style?.layer(withIdentifier: "clouds"))
}
func testMGLHillshadeStyleLayer() {
let source = MGLRasterDEMSource(identifier: "dem", tileURLTemplates: ["https://example.com/raster-rgb/{z}/{x}/{y}.png"], options: [
.minimumZoomLevel: 9,
.maximumZoomLevel: 16,
.tileSize: 256,
.attributionInfos: [
MGLAttributionInfo(title: NSAttributedString(string: "© Mapbox"), url: URL(string: "https://mapbox.com"))
]
])
mapView.style?.addSource(source)
let canals = MGLVectorTileSource(identifier: "canals", configurationURL: URL(string: "https://example.com/style.json")!)
mapView.style?.addSource(canals)
let canalShadowLayer = MGLLineStyleLayer(identifier: "waterway-river-canal-shadow", source: canals)
mapView.style?.addLayer(canalShadowLayer)
//#-example-code
let layer = MGLHillshadeStyleLayer(identifier: "hills", source: source)
layer.hillshadeExaggeration = NSExpression(forConstantValue: 0.6)
if let canalShadowLayer = mapView.style?.layer(withIdentifier: "waterway-river-canal-shadow") {
mapView.style?.insertLayer(layer, below: canalShadowLayer)
}
//#-end-example-code
XCTAssertNotNil(mapView.style?.layer(withIdentifier: "hills"))
}
func testMGLVectorStyleLayer$predicate() {
let terrain = MGLVectorTileSource(identifier: "terrain", configurationURL: URL(string: "https://example.com/style.json")!)
mapView.style?.addSource(terrain)
//#-example-code
let layer = MGLLineStyleLayer(identifier: "contour", source: terrain)
layer.sourceLayerIdentifier = "contours"
layer.predicate = NSPredicate(format: "(index == 5 || index == 10) && CAST(ele, 'NSNumber') >= 1500.0")
mapView.style?.addLayer(layer)
//#-end-example-code
XCTAssertNotNil(mapView.style?.layer(withIdentifier: "contour"))
}
func testMGLMapView() {
//#-example-code
#if os(macOS)
class MapClickGestureRecognizer: NSClickGestureRecognizer {
override func shouldRequireFailure(of otherGestureRecognizer: NSGestureRecognizer) -> Bool {
return otherGestureRecognizer is NSClickGestureRecognizer
}
}
#else
let mapTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(myCustomFunction))
for recognizer in mapView.gestureRecognizers! where recognizer is UITapGestureRecognizer {
mapTapGestureRecognizer.require(toFail: recognizer)
}
mapView.addGestureRecognizer(mapTapGestureRecognizer)
#endif
//#-end-example-code
}
func testMGLMapSnapshotter() {
let expectation = self.expectation(description: "MGLMapSnapshotter should produce a snapshot")
#if os(macOS)
var image: NSImage? {
didSet {
expectation.fulfill()
}
}
#else
var image: UIImage? {
didSet {
expectation.fulfill()
}
}
#endif
class MGLStyle {
static var satelliteStreetsStyleURL: URL {
return MGLDocumentationExampleTests.styleURL
}
}
//#-example-code
let camera = MGLMapCamera(lookingAtCenter: CLLocationCoordinate2D(latitude: 37.7184, longitude: -122.4365), altitude: 100, pitch: 20, heading: 0)
let options = MGLMapSnapshotOptions(styleURL: MGLStyle.satelliteStreetsStyleURL, camera: camera, size: CGSize(width: 320, height: 480))
options.zoomLevel = 10
let snapshotter = MGLMapSnapshotter(options: options)
snapshotter.start { (snapshot, error) in
if let error = error {
fatalError(error.localizedDescription)
}
image = snapshot?.image
}
//#-end-example-code
wait(for: [expectation], timeout: 5)
}
func testMGLCluster() {
enum ExampleError: Error {
case unexpectedFeatureType
case featureIsNotACluster
}
let geoJSON: [String: Any] = [
"type" : "Feature",
"geometry" : [
"coordinates" : [
-77.00896639534831,
38.87031006108791,
0.0
],
"type" : "Point"
],
"properties" : [
"cluster" : true,
"cluster_id" : 123,
"point_count" : 4567,
]
]
let clusterShapeData = try! JSONSerialization.data(withJSONObject: geoJSON, options: [])
do {
//#-example-code
let shape = try! MGLShape(data: clusterShapeData, encoding: String.Encoding.utf8.rawValue)
guard let pointFeature = shape as? MGLPointFeature else {
throw ExampleError.unexpectedFeatureType
}
// Check for cluster conformance
guard let cluster = pointFeature as? MGLCluster else {
throw ExampleError.featureIsNotACluster
}
// Currently the only supported class that conforms to `MGLCluster` is
// `MGLPointFeatureCluster`
guard cluster is MGLPointFeatureCluster else {
throw ExampleError.unexpectedFeatureType
}
//#-end-example-code
XCTAssert(cluster.clusterIdentifier == 123)
XCTAssert(cluster.clusterPointCount == 4567)
}
catch let error {
XCTFail("Example failed with thrown error: \(error)")
}
}
func testMGLAttributedExpression() {
//#-example-code
#if os(macOS)
let redColor = NSColor.red
#else
let redColor = UIColor.red
#endif
let expression = NSExpression(forConstantValue: "Foo")
let attributes: [MGLAttributedExpressionKey: NSExpression] = [.fontNamesAttribute : NSExpression(forConstantValue: ["DIN Offc Pro Italic",
"Arial Unicode MS Regular"]),
.fontScaleAttribute: NSExpression(forConstantValue: 1.2),
.fontColorAttribute: NSExpression(forConstantValue: redColor)]
let attributedExpression = MGLAttributedExpression(expression, attributes:attributes)
//#-end-example-code
XCTAssertNotNil(attributedExpression)
}
// For testMGLMapView().
func myCustomFunction() {}
}