@masterportal/masterportalapi 2.7.0 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,13 +5,38 @@
5
5
 
6
6
  ## Unreleased - in development
7
7
  ### Added
8
+
8
9
  ### Changed
9
10
 
10
11
  ### Deprecated
11
12
 
12
13
  ### Removed
14
+
13
15
  ### Fixed
14
16
 
17
+ ---
18
+
19
+ ## 2.9.0 - 2022-10-25
20
+ ### Added
21
+
22
+ ### Changed
23
+ - Exports in src/rawLayerList and src/crs.js changed to module.exports to provide the possibility to stub the functions with sinon in masterportal.
24
+
25
+ ### Deprecated
26
+
27
+ ### Removed
28
+
29
+ ### Fixed
30
+
31
+ ---
32
+
33
+ ## 2.8.0 - 2022-10-04
34
+ ### Added
35
+ - Issue #814: Adding optional error callbacks and availability detection to layers.
36
+ - Added possibility to set initial features for layer vectorBase.
37
+
38
+ ---
39
+
15
40
  ## 2.7.0 - 2022-09-14
16
41
  ### Changed
17
42
  - The following packages have been updated:
package/README.md CHANGED
@@ -10,6 +10,13 @@ If you want to create a 3d map, you have to provide the peer dependency cesium.
10
10
 
11
11
  By importing the project by module name like ``import ... from "masterportalAPI"``, most bundlers and bundler configurations will include the whole masterportalAPI. If you only need a subset of the provided functions and want to keep your build clean, directly import the needed functions like ``import {createMap} from "masterportalAPI/src/map.js``.
12
12
 
13
+ ## Error Handling
14
+
15
+ 1. Error event callback: The methods `createMap` and `addLayer` both optionally accept error event callbacks that are called with OpenLayers error events (tileloaderror, imageloaderror, featuresloaderror, error).
16
+ 2. Ping: A method that returns a Promise. This resolves to the status code of the layer's GetCapabilities request, which can be used to decide if the service is reachable and usable at all.
17
+
18
+ Both methods may be required for useful user feedback. For example, a WMS may answer a tile request with 404 if no tile is available for a region, but may be fully available within its specification. This may throw an error while the service itself is available as specified. It is up to the using implementation how to react, how often to ping, and so on.
19
+
13
20
  ## Scripts
14
21
 
15
22
  |Script|Effect|
@@ -47,7 +47,7 @@
47
47
  {
48
48
  "id": "4001",
49
49
  "name": "Gelaende",
50
- "url": "https://daten-hamburg.de/gdi3d/datasource-data/Gelaende ",
50
+ "url": "https://daten-hamburg.de/gdi3d/datasource-data/Gelaende",
51
51
  "typ": "Terrain3D",
52
52
  "cesiumTerrainProviderOptions": {
53
53
  "requestVertexNormals": true
package/example/index.js CHANGED
@@ -11,6 +11,10 @@ import portalConfig from "./config/portal.json";
11
11
  import localGeoJSON from "./config/localGeoJSON.js";
12
12
  import {load3DScript} from "../src/lib/load3DScript";
13
13
 
14
+ function errorCallback (errorEvent) {
15
+ console.error("This is an error event:", errorEvent);
16
+ }
17
+
14
18
  //* Add elements to window to play with API in console
15
19
  window.mpapi = {
16
20
  ...mpapi,
@@ -94,7 +98,7 @@ document.getElementById("layer-visibility").addEventListener("click", function (
94
98
  });
95
99
  });
96
100
 
97
- map2D = mapsAPI.map.createMap(config, "2D");
101
+ map2D = mapsAPI.map.createMap(config, "2D", {errorCallback});
98
102
  services.push(localService);
99
103
  // */
100
104
 
@@ -148,7 +152,18 @@ services.find(({id}) => id === "5001").style = styleOaf;
148
152
  //* SYNCHRONOUS EXAMPLE: layerConf is known
149
153
  window.mpapi.map = map2D;
150
154
 
151
- ["2001", "2002", "1002", "3001", "5001"].forEach(id => window.mpapi.map.addLayer(id));
155
+ ["2001", "2002", "1002", "3001", "5001"].forEach(id => window.mpapi.map.addLayer(id, {errorCallback}));
156
+
157
+ // ping all layers
158
+ services
159
+ .map(service => ({
160
+ ping: mpapi.ping(service),
161
+ service
162
+ }))
163
+ .forEach(({ping, service}) => ping
164
+ // eslint-disable-next-line no-console
165
+ .then(statusCode => console.log(`Service ${service.id} pinged; returned ${statusCode}.`))
166
+ .catch(console.error));
152
167
 
153
168
  // */
154
169
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@masterportal/masterportalapi",
3
3
  "author": "Implementierungspartnerschaft Masterportal <info@masterportal.org> (https://www.masterportal.org)",
4
- "version": "2.7.0",
4
+ "version": "2.9.0",
5
5
  "license": "MIT",
6
6
  "description": "Basic functions of the Masterportal as api",
7
7
  "repository": {
package/src/crs.js CHANGED
@@ -22,7 +22,7 @@ function addAliasForWFSFromGeoserver (epsgCode) {
22
22
  * @param {string} name - projection name as written in [0] position of namedProjections
23
23
  * @returns {(object|undefined)} proj4 projection object or undefined
24
24
  */
25
- export function getProjection (name) {
25
+ function getProjection (name) {
26
26
  return proj4.defs(name);
27
27
  }
28
28
 
@@ -33,7 +33,7 @@ export function getProjection (name) {
33
33
  ]] - projection name, projection definition string
34
34
  * @returns {undefined}
35
35
  */
36
- export function registerProjections (namedProjections = defaults.namedProjections) {
36
+ function registerProjections (namedProjections = defaults.namedProjections) {
37
37
  proj4.defs(namedProjections);
38
38
  register(proj4);
39
39
  namedProjections.forEach(projection => {
@@ -48,7 +48,7 @@ export function registerProjections (namedProjections = defaults.namedProjection
48
48
  * Returns all known projections.
49
49
  * @returns {object[]} array of projection objects with their name added
50
50
  */
51
- export function getProjections () {
51
+ function getProjections () {
52
52
  const projections = Object
53
53
  .keys(proj4.defs)
54
54
  .map(name => Object.assign(proj4.defs(name), {name}));
@@ -64,7 +64,7 @@ export function getProjections () {
64
64
  * @param {ol.Map} map - map to get projection of
65
65
  * @returns {string} active projection name of map
66
66
  */
67
- export function getMapProjection (map) {
67
+ function getMapProjection (map) {
68
68
  return map.getView().getProjection().getCode();
69
69
  }
70
70
 
@@ -87,7 +87,7 @@ function getProj4Projection (projection) {
87
87
  * @param {number[]} point - point to project
88
88
  * @returns {number[]|undefined} transformed point
89
89
  */
90
- export function transform (sourceProjection, targetProjection, point) {
90
+ function transform (sourceProjection, targetProjection, point) {
91
91
  const source = getProj4Projection(sourceProjection),
92
92
  target = getProj4Projection(targetProjection);
93
93
 
@@ -106,7 +106,7 @@ export function transform (sourceProjection, targetProjection, point) {
106
106
  * @param {number[]} point - point to project
107
107
  * @returns {number[]|undefined} new point or undefined
108
108
  */
109
- export function transformToMapProjection (map, sourceProjection, point) {
109
+ function transformToMapProjection (map, sourceProjection, point) {
110
110
  return transform(sourceProjection, getMapProjection(map), point);
111
111
  }
112
112
 
@@ -117,6 +117,16 @@ export function transformToMapProjection (map, sourceProjection, point) {
117
117
  * @param {number[]} point - point to project
118
118
  * @returns {(number[]|undefined)} new point or undefined
119
119
  */
120
- export function transformFromMapProjection (map, targetProjection, point) {
120
+ function transformFromMapProjection (map, targetProjection, point) {
121
121
  return transform(getMapProjection(map), targetProjection, point);
122
122
  }
123
+
124
+ module.exports = {
125
+ transformFromMapProjection,
126
+ transformToMapProjection,
127
+ transform,
128
+ getMapProjection,
129
+ getProjections,
130
+ registerProjections,
131
+ getProjection
132
+ };
package/src/index.js CHANGED
@@ -14,6 +14,7 @@ import Tileset from "./layer/tileset";
14
14
  import * as layerLib from "./layer/lib";
15
15
  import {search, setGazetteerUrl} from "./searchAddress";
16
16
  import setBackgroundImage from "./lib/setBackgroundImage";
17
+ import ping from "./lib/ping";
17
18
 
18
19
  export {
19
20
  createMapView,
@@ -31,6 +32,7 @@ export {
31
32
  setBackgroundImage,
32
33
  setGazetteerUrl,
33
34
  rawLayerList,
35
+ ping,
34
36
  search,
35
37
  crs
36
38
  };
package/src/layer/wms.js CHANGED
@@ -5,7 +5,7 @@ import TileGrid from "ol/tilegrid/TileGrid";
5
5
  import ImageWMS from "ol/source/ImageWMS.js";
6
6
  import {get as getProjection} from "ol/proj";
7
7
 
8
- import {getLayerWhere} from "../rawLayerList";
8
+ import {rawLayerList} from "../index.js";
9
9
 
10
10
  const OLCS_DEFAULT_OLCS_PROJECTION_CRS = "EPSG:3857";
11
11
 
@@ -176,7 +176,7 @@ export function updateSource (layer) {
176
176
  * @returns {(string|undefined)} the gfiURL, or undefined if it could not be constructed
177
177
  */
178
178
  export function getGfiURL (layer, map, coordinate) {
179
- const rawLayer = getLayerWhere({Id: layer.get("id")}),
179
+ const rawLayer = rawLayerList.getLayerWhere({Id: layer.get("id")}),
180
180
  resolution = map.getView().getResolution(),
181
181
  projection = map.getView().getProjection(),
182
182
  params = Object.assign({
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Helper function to detect layer availability/usability. Depending on layer
3
+ * type, the method to infer availability broadly varies.
4
+ * @param {Object} layerSpecification services.json layer specification
5
+ * @returns {Promise} resolves to status code of a layer-specific default
6
+ * request as Number, or null if no request could be made, which should
7
+ * indicate misconfiguration; defaults to 900 if the request itself failed for
8
+ * arbitrary reasons (usually CORS-related)
9
+ */
10
+ export default function ({url, typ, capabilitiesUrl, ...rest}) {
11
+ const statusCheckUrls = [];
12
+
13
+ if (capabilitiesUrl) {
14
+ statusCheckUrls.push(capabilitiesUrl);
15
+ }
16
+ else if (typ === "OAF") {
17
+ statusCheckUrls.push(url);
18
+ }
19
+ else if (typ === "Entities3D") {
20
+ rest.entities
21
+ .forEach(entity => entity.url ? statusCheckUrls.push(entity.url) : null);
22
+ }
23
+ else if (typ === "TileSet3D" || typ === "Terrain3D") {
24
+ statusCheckUrls.push(url);
25
+ }
26
+ else if (typ === "GeoJSON") {
27
+ if (!url && rest.features) {
28
+ // may be a local layer without source url – that's equal to 200
29
+ return Promise.resolve(200);
30
+ }
31
+ if (url) {
32
+ statusCheckUrls.push(url);
33
+ }
34
+ }
35
+ else if (typ === "VectorTile") {
36
+ statusCheckUrls.push(url.replaceAll(/\{[xyz]\}/ig, "0"));
37
+ }
38
+ else if (typ === "WMTS") {
39
+ const arbitraryTileRequests = (url ? [url] : rest.urls)
40
+ .map(entry => entry
41
+ .replace(/\{Style\}/ig, rest.style)
42
+ .replace(/\{TileMatrixSet\}/ig, rest.tileMatrixSet)
43
+ .replaceAll(/\{(TileMatrix|TileRow|TileCol)\}/ig, "0"));
44
+
45
+ statusCheckUrls.push(...arbitraryTileRequests);
46
+ }
47
+ else if (url) {
48
+ statusCheckUrls.push(`${url}?service=${typ}&request=GetCapabilities`);
49
+ }
50
+
51
+ if (!statusCheckUrls.length) {
52
+ return Promise.resolve(null);
53
+ }
54
+
55
+ return Promise
56
+ .allSettled(statusCheckUrls
57
+ .map(statusCheckUrl => new Promise((resolve) => fetch(statusCheckUrl, {method: "HEAD"})
58
+ .then(({status}) => resolve(status))
59
+ // usually from CORS, treated as 900 here ("proprietary" status code)
60
+ .catch(() => resolve(900)))))
61
+ // return highest status code (will indicate erroneous behaviour)
62
+ .then(codes => Math.max(...codes.map(({value}) => value)));
63
+ }
package/src/maps/map.js CHANGED
@@ -20,6 +20,7 @@ export default {
20
20
  * @param {object} [settings={}] - settings object
21
21
  * @param {object} [settings.mapParams] - additional parameter object that is spread into the ol.Map constructor object
22
22
  * @param {function} [settings.callback] - optional callback for layer list loading
23
+ * @param {function} [settings.errorCallback] - optional callback for layer error events
23
24
  * @returns {module:ol/Map~Map} The map.
24
25
  */
25
26
  createMap: function (config, mapMode = "2D", settings = {}) {
@@ -11,7 +11,7 @@ import * as vectorBase from "../../layer/vectorBase";
11
11
  import * as vectortile from "../../layer/vectorTile";
12
12
  import * as oaf from "../../layer/oaf";
13
13
  import {createMapView} from "../../maps/mapView";
14
- import {initializeLayerList, getLayerWhere} from "../../rawLayerList";
14
+ import {rawLayerList} from "../../index.js";
15
15
  import {registerProjections} from "../../crs";
16
16
  import {setGazetteerUrl} from "../../searchAddress";
17
17
 
@@ -32,6 +32,48 @@ const layerBuilderMap = {
32
32
  },
33
33
  originalAddLayer = Map.prototype.addLayer;
34
34
 
35
+ /**
36
+ * Flattens a Layer/LayerGroup to an array of non-group instances.
37
+ * @param {ol/layer/Base} layer any layer, possibly a LayerGroup
38
+ * @returns {ol/layer/Base[]} array of all given layers
39
+ */
40
+ function flattenLayerGroups (layer) {
41
+ return layer.getLayers
42
+ ? layer
43
+ .getLayers()
44
+ .getArray()
45
+ .map(l => flattenLayerGroups(l))
46
+ .flat(1)
47
+ : [layer];
48
+ }
49
+
50
+ /**
51
+ * Adds an error handling function to an arbitrary layer. The event will be
52
+ * registered to the tileloaderror, imageloaderror, featuresloaderror, and
53
+ * generic error event. Depending on the type of layer and error, only one of
54
+ * these events will fire.
55
+ * @param {ol/layer/Base} layer Any type of layer implemented.
56
+ * @param {Function} errorCallback Error callback, called from OL event.
57
+ * @returns {void} side-effect to layer
58
+ */
59
+ function injectErrorCallback (layer, errorCallback) {
60
+ const layers = flattenLayerGroups(layer);
61
+
62
+ layers.forEach(l => {
63
+ const source = l.getSource?.();
64
+
65
+ if (source) {
66
+ source.on?.("tileloaderror", errorCallback);
67
+ source.on?.("imageloaderror", errorCallback);
68
+ source.on?.("featuresloaderror", errorCallback);
69
+ source.on?.("error", errorCallback);
70
+ }
71
+ else {
72
+ console.error("Could not register error callback on layer:", l);
73
+ }
74
+ });
75
+ }
76
+
35
77
  /**
36
78
  * Adds a layer to the map, or adds a layer to the map by id.
37
79
  * This id is looked up within the array of all known services.
@@ -40,18 +82,22 @@ const layerBuilderMap = {
40
82
  * if you request the services from the internet.
41
83
  *
42
84
  * This function is available on all ol/Map instances.
43
- * @param {(string|ol/Layer)} layerOrId - if of layer to add to map
85
+ * @param {(string|ol/layer/Base)} layerOrId - if of layer to add to map
44
86
  * @param {object} [params] - optional parameter object
45
87
  * @param {boolean} [params.visibility=true] - whether added layer is initially visible
46
88
  * @param {Number} [params.transparency=0] - how visible the layer is initially
89
+ * @param {Function} [params.errorCallback=console.error] - callback for layer source error events
47
90
  * @returns {?ol.Layer} added layer
48
91
  */
49
- function addLayer (layerOrId, params = {visibility: true, transparency: 0}) {
50
- var layer, layerBuilder;
92
+ function addLayer (layerOrId, params = {visibility: true, transparency: 0, errorCallback: console.error}) {
93
+ const errorCallback = typeof params.errorCallback === "function"
94
+ ? params.errorCallback
95
+ : console.error;
96
+ let layer, layerBuilder;
51
97
 
52
98
  // if parameter is id, create and add layer with masterportalAPI mechanisms
53
99
  if (typeof layerOrId === "string") {
54
- const rawLayer = getLayerWhere({id: layerOrId});
100
+ const rawLayer = rawLayerList.getLayerWhere({id: layerOrId});
55
101
 
56
102
  if (!rawLayer) {
57
103
  console.error("Layer with id '" + layerOrId + "' not found. No layer added to map.");
@@ -65,11 +111,13 @@ function addLayer (layerOrId, params = {visibility: true, transparency: 0}) {
65
111
  layer = layerBuilder.createLayer(rawLayer, {}, {map: this});
66
112
  layer.setVisible(typeof params.visibility === "boolean" ? params.visibility : true);
67
113
  layer.setOpacity(typeof params.transparency === "number" ? (100 - params.transparency) / 100 : 1);
114
+ injectErrorCallback(layer, errorCallback);
68
115
  originalAddLayer.call(this, layer);
69
116
  return layer;
70
117
  }
71
118
 
72
119
  // else use original function
120
+ injectErrorCallback(layerOrId, errorCallback);
73
121
  return originalAddLayer.call(this, layerOrId);
74
122
  }
75
123
 
@@ -93,10 +141,11 @@ Map.prototype.addLayer = addLayer;
93
141
  * @param {object} [settings={}] - setings object
94
142
  * @param {object} [settings.mapParams] - additional parameter object that is spread into the ol.Map constructor object
95
143
  * @param {function} [settings.callback] - optional callback for layer list loading
144
+ * @param {function} [settings.errorCallback] – method called on error events
96
145
  * @param {String} [mapMode = "2D"] The map mode. '2D' to craete a 2D-map and '3D' to create a 3D-map.
97
146
  * @returns {object} map object from ol
98
147
  */
99
- export function createMap (config = defaults, {mapParams, callback} = {}) {
148
+ export function createMap (config = defaults, {mapParams, callback, errorCallback} = {}) {
100
149
  registerProjections(config.namedProjections);
101
150
  setBackgroundImage(config);
102
151
  setGazetteerUrl(config.gazetteerUrl);
@@ -111,10 +160,10 @@ export function createMap (config = defaults, {mapParams, callback} = {}) {
111
160
  map.set("id", `map2D_${mapIdCounter++}`);
112
161
 
113
162
  // extend callback to load configured initial layers
114
- initializeLayerList(config.layerConf, (param, error) => {
163
+ rawLayerList.initializeLayerList(config.layerConf, (param, error) => {
115
164
  getInitialLayers(config)
116
165
  .forEach(layer => {
117
- map.addLayer(layer.id, layer);
166
+ map.addLayer(layer.id, {errorCallback});
118
167
  });
119
168
 
120
169
  if (typeof callback === "function") {
@@ -14,7 +14,7 @@ let layerList = [];
14
14
  * @param {function} [callback] - called with services after loaded; called with false and error on error
15
15
  * @returns {undefined} nothing, add callback to receive layerList
16
16
  */
17
- export function initializeLayerList (layerConf = defaults.layerConf, callback) {
17
+ function initializeLayerList (layerConf = defaults.layerConf, callback) {
18
18
  if (Array.isArray(layerConf)) {
19
19
  // case: parameter was services.json contents
20
20
  layerList = layerConf;
@@ -55,14 +55,14 @@ export function initializeLayerList (layerConf = defaults.layerConf, callback) {
55
55
  * @param {object} searchAttributes - key/value-pairs to be searched for, e.g. { typ: "WMS" } to get the first WMS
56
56
  * @returns {?object} first layer matching the searchAttributes or null if none was found
57
57
  */
58
- export function getLayerWhere (searchAttributes) {
58
+ function getLayerWhere (searchAttributes) {
59
59
  const keys = Object.keys(searchAttributes);
60
60
 
61
61
  return layerList.find(entry => keys.every(key => entry[key] === searchAttributes[key])) || null;
62
62
  }
63
63
 
64
64
  /** @returns {object[]} complete layerList as initialized */
65
- export function getLayerList () {
65
+ function getLayerList () {
66
66
  return layerList;
67
67
  }
68
68
 
@@ -72,7 +72,7 @@ export function getLayerList () {
72
72
  * @param {string} [featureAttribute] - if given, only one entry of map is returned
73
73
  * @returns {?(object|string)} - map of originalName->displayName or name of featureAttribute if specified; if layer or featureAttribute not found, null
74
74
  */
75
- export function getDisplayNamesOfFeatureAttributes (layerId, featureAttribute) {
75
+ function getDisplayNamesOfFeatureAttributes (layerId, featureAttribute) {
76
76
  const attributes = getLayerWhere({id: layerId});
77
77
 
78
78
  if (attributes && typeof featureAttribute === "string") {
@@ -86,3 +86,11 @@ export function getDisplayNamesOfFeatureAttributes (layerId, featureAttribute) {
86
86
 
87
87
  return null;
88
88
  }
89
+
90
+
91
+ module.exports = {
92
+ getLayerWhere,
93
+ getDisplayNamesOfFeatureAttributes,
94
+ initializeLayerList,
95
+ getLayerList
96
+ };
@@ -3,7 +3,7 @@ import {getWidth} from "ol/extent";
3
3
 
4
4
  import * as wmts from "../../src/layer/wmts";
5
5
 
6
- describe.only("wmts.js", function () {
6
+ describe("wmts.js", function () {
7
7
  describe("generateArrays", function () {
8
8
  it("should fill the arrays resolutions and matrixIds with numbers", function () {
9
9
  const size = getWidth(getProjection("EPSG:3857").getExtent()) / 256,
@@ -0,0 +1,69 @@
1
+ import ping from "../../src/lib/ping";
2
+
3
+ const originalFetch = global.fetch;
4
+
5
+ describe("ping.js", function () {
6
+ let mockFetch;
7
+
8
+ beforeEach(() => {
9
+ mockFetch = jest.fn(() => Promise.resolve({
10
+ status: 200
11
+ }));
12
+ global.fetch = mockFetch;
13
+ });
14
+
15
+ afterAll(() => {
16
+ global.fetch = originalFetch;
17
+ });
18
+
19
+ describe("ping", function () {
20
+ it("calls an url from the service definition", async function () {
21
+ const status = await ping({url: "www.example.com", typ: "WMS"});
22
+
23
+ expect(status).toBe(200);
24
+ expect(mockFetch).toHaveBeenCalledWith("www.example.com?service=WMS&request=GetCapabilities", {method: "HEAD"});
25
+ });
26
+
27
+ it("calls 0/0/0 tile for VectorTile services", async function () {
28
+ const status = await ping({url: "www.example.com/{x}/{y}/{z}", typ: "VectorTile"});
29
+
30
+ expect(status).toBe(200);
31
+ expect(mockFetch).toHaveBeenCalledWith("www.example.com/0/0/0", {method: "HEAD"});
32
+ });
33
+
34
+ it("calls the URL directly for OAF services", async function () {
35
+ const status = await ping({url: "www.example.com", typ: "OAF"});
36
+
37
+ expect(status).toBe(200);
38
+ expect(mockFetch).toHaveBeenCalledWith("www.example.com", {method: "HEAD"});
39
+ });
40
+
41
+ it("returns 900 on network errors", async function () {
42
+ mockFetch = jest.fn(() => Promise.resolve({status: 900}));
43
+ global.fetch = mockFetch;
44
+
45
+ const status = await ping({url: "www.example.com", typ: "OAF"});
46
+
47
+ expect(status).toBe(900);
48
+ expect(mockFetch).toHaveBeenCalledWith("www.example.com", {method: "HEAD"});
49
+ });
50
+
51
+ it("returns highest status code on multiple urls", async function () {
52
+ let counter = 200;
53
+
54
+ mockFetch = jest.fn(() => Promise.resolve({status: counter++}));
55
+ global.fetch = mockFetch;
56
+
57
+ const status = await ping({urls: [
58
+ "www.example.com",
59
+ "www.example.com/but"
60
+ ], typ: "WMTS"});
61
+
62
+ expect(status).toBe(201);
63
+ expect(mockFetch)
64
+ .toHaveBeenCalledWith("www.example.com", {method: "HEAD"});
65
+ expect(mockFetch)
66
+ .toHaveBeenCalledWith("www.example.com/but", {method: "HEAD"});
67
+ });
68
+ });
69
+ });