mobility-toolbox-js 1.5.0 → 1.6.0-beta.10

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.
@@ -2,7 +2,7 @@ import { translateTrajStationsResp } from './TrajservAPIUtils';
2
2
 
3
3
  describe('TrajservAPIUtils', () => {
4
4
  describe('#translateTrajStationsResp', () => {
5
- test.only("should success even if data doesn't have all properties set", () => {
5
+ test("should success even if data doesn't have all properties set", () => {
6
6
  const empty = {
7
7
  backgroundColor: undefined,
8
8
  bicyclesAllowed: false,
@@ -44,7 +44,7 @@ class TralisAPI {
44
44
  * @param {string} options.url Service url.
45
45
  * @param {string} options.apiKey Access key for [geOps services](https://developer.geops.io/).
46
46
  * @param {string} [options.prefix=''] Service prefix to specify tenant.
47
- * @param {string} [options.projection='epsg:3857'] The epsg code of the projection for features.
47
+ * @param {string} [options.projection] The epsg code of the projection for features.
48
48
  * @param {number[4]} [options.bbox=[minX, minY, maxX, maxY] The bounding box to receive data from.
49
49
  */
50
50
  constructor(options = {}) {
@@ -60,6 +60,12 @@ class TralisAPI {
60
60
  wsUrl = `${wsUrl}?key=${options.apiKey}`;
61
61
  }
62
62
 
63
+ /** @ignore */
64
+ this.bbox = options.bbox;
65
+
66
+ /** @ignore */
67
+ this.projection = options.projection || 'EPSG:3857';
68
+
63
69
  /** @ignore */
64
70
  this.subscribedStationUic = null;
65
71
 
@@ -75,15 +81,47 @@ class TralisAPI {
75
81
  /** @ignore */
76
82
  this.prefix = options.prefix || '';
77
83
 
84
+ this.isUpdateBboxOnMoveEnd = options.isUpdateBboxOnMoveEnd || false;
85
+
78
86
  /** @ignore */
79
87
  this.conn = new WebSocketConnector(wsUrl);
80
- this.conn.setProjection(options.projection || 'epsg:3857');
81
88
 
82
- if (options.bbox) {
83
- this.conn.setBbox(options.bbox);
89
+ if (options.projection) {
90
+ this.setProjection(options.projection);
84
91
  }
85
92
  }
86
93
 
94
+ /**
95
+ * Send the projection to use. Default to EPSG:3857.
96
+ *
97
+ * @param {string} epsgCode The EPSG code of the projection.
98
+ */
99
+ setProjection(epsgCode) {
100
+ this.conn.send(`PROJECTION ${epsgCode}`);
101
+ }
102
+
103
+ /**
104
+ * Send the bbox to the service, if the first time it will also subscribe to trajectory and deleted_vehicles channels.
105
+ *
106
+ * @param {number[5]} bbox The bounding box and the zoom level to receive data from.
107
+ */
108
+ setBbox(bbox) {
109
+ /**
110
+ * The BBOX for websocket responses
111
+ * @type {Array<number>}
112
+ */
113
+ this.bbox = bbox;
114
+ this.conn.send(`BBOX ${bbox.join(' ')}`);
115
+ }
116
+
117
+ /**
118
+ * Unsubscribe trajectory and deleted_vehicles channels. To resubscribe you have to set a new BBOX.
119
+ */
120
+ // eslint-disable-next-line class-methods-use-this
121
+ reset() {
122
+ this.conn.send('RESET');
123
+ }
124
+
87
125
  /**
88
126
  * Subscribe to a channel.
89
127
  *
@@ -98,7 +136,7 @@ class TralisAPI {
98
136
  }
99
137
 
100
138
  /**
101
- * Unsubscribe to a channel.
139
+ * Unsubscribe both modes of a channel.
102
140
  *
103
141
  * @param {string} channel Name of the websocket channel to unsubscribe.
104
142
  * @param {string} [suffix=''] Suffix to add to the channel name.
@@ -377,7 +415,12 @@ class TralisAPI {
377
415
  */
378
416
  subscribeTrajectory(mode, onMessage) {
379
417
  this.unsubscribeTrajectory(onMessage);
380
- this.subscribe(`trajectory${getModeSuffix(mode, TralisModes)}`, onMessage);
418
+ this.subscribe(
419
+ `trajectory${getModeSuffix(mode, TralisModes)}`,
420
+ onMessage,
421
+ null,
422
+ this.isUpdateBboxOnMoveEnd,
423
+ );
381
424
  }
382
425
 
383
426
  /**
@@ -399,6 +442,8 @@ class TralisAPI {
399
442
  this.subscribe(
400
443
  `deleted_vehicles${getModeSuffix(mode, TralisModes)}`,
401
444
  onMessage,
445
+ null,
446
+ this.isUpdateBboxOnMoveEnd,
402
447
  );
403
448
  }
404
449
 
@@ -1,5 +1,7 @@
1
1
  /**
2
- * Class use to facilitate connection to a WebSocket
2
+ * Class use to facilitate connection to a WebSocket.
3
+ * This class must not conatain any specific WebSocket implementation.
4
+ *
3
5
  * @private
4
6
  */
5
7
  class WebSocketConnector {
@@ -7,19 +9,29 @@ class WebSocketConnector {
7
9
  /**
8
10
  * Array of subscriptions.
9
11
  * @type {Array<subscription>}
12
+ * @private
10
13
  */
11
14
  this.subscriptions = [];
15
+
16
+ /**
17
+ * List of channels subscribed.
18
+ * @type {Array<subscription>}
19
+ * @private
20
+ */
21
+ this.subscribed = {};
22
+
23
+ // Connect the websocket
12
24
  this.connect(url);
13
25
 
14
26
  // keep websocket alive
15
27
  setInterval(() => {
16
28
  this.send('PING');
17
29
  }, 10000);
18
- this.subscribed = {};
19
30
  }
20
31
 
21
32
  /**
22
33
  * Get the websocket request string.
34
+ *
23
35
  * @param {string} method Request mehtod {GET, SUB}.
24
36
  * @param {Object} params Request parameters.
25
37
  * @param {string} params.channel Channel name
@@ -37,6 +49,7 @@ class WebSocketConnector {
37
49
 
38
50
  /**
39
51
  * (Re)connect the websocket.
52
+ *
40
53
  * @param {string} url url to connect to
41
54
  * @private
42
55
  */
@@ -48,16 +61,8 @@ class WebSocketConnector {
48
61
  /** @ignore */
49
62
  this.websocket = new WebSocket(url);
50
63
 
51
- if (this.currentProj) {
52
- this.setProjection(this.currentProj);
53
- }
54
-
55
- if (this.currentBbox) {
56
- this.setBbox(this.currentBbox);
57
- }
58
-
59
64
  [...this.subscriptions].forEach((s) => {
60
- this.subscribe(s.params, s.cb, s.errorCb, true);
65
+ this.subscribe(s.params, s.cb, s.errorCb, s.quiet);
61
66
  });
62
67
 
63
68
  // reconnect on close
@@ -70,6 +75,7 @@ class WebSocketConnector {
70
75
 
71
76
  /**
72
77
  * Sends a get request to the websocket.
78
+ *
73
79
  * @param {Object} params Parameters for the websocket get request
74
80
  * @param {function} cb callback on listen
75
81
  * @param {function} errorCb Callback on error
@@ -83,6 +89,7 @@ class WebSocketConnector {
83
89
 
84
90
  /**
85
91
  * Sends a message to the websocket.
92
+ *
86
93
  * @param {message} message Message to send.
87
94
  * @private
88
95
  */
@@ -90,7 +97,6 @@ class WebSocketConnector {
90
97
  const send = () => {
91
98
  this.websocket.send(message);
92
99
  };
93
-
94
100
  if (this.websocket.readyState === WebSocket.CONNECTING) {
95
101
  this.websocket.addEventListener('open', send);
96
102
  } else {
@@ -99,50 +105,20 @@ class WebSocketConnector {
99
105
  }
100
106
 
101
107
  /**
102
- * Set the projection for websocket responses.
103
- * @param {string} value projection value to be set
104
- * @private
105
- */
106
- setProjection(value) {
107
- /**
108
- * The projection for websocket responses
109
- * @type {string}
110
- */
111
- this.currentProj = value;
112
- this.send(`PROJECTION ${value}`);
113
- }
114
-
115
- /**
116
- * Set the BBOX for websocket responses.
117
- * @param {Array<Array<number>>} coordinates array of coordinates
118
- * @private
119
- */
120
- setBbox(coordinates) {
121
- /**
122
- * The BBOX for websocket responses
123
- * @type {Array<Array<number>>}
124
- */
125
- this.currentBbox = coordinates;
126
- this.send(`BBOX ${coordinates.join(' ')}`);
127
- this.subscriptions.forEach((s) => {
128
- this.get(s.params, s.cb, s.errorCb);
129
- });
130
- }
131
-
132
- /**
133
- * Listen to websocket responses.
134
- * @private
108
+ * Listen to websocket messages.
109
+ *
135
110
  * @param {Object} params Parameters for the websocket get request
136
111
  * @param {function} cb callback on listen
137
112
  * @param {function} errorCb Callback on error
138
113
  * @returns {{onMessage: function, errorCb: function}} Object with onMessage and error callbacks
114
+ * @private
139
115
  */
140
116
  listen(params, cb, errorCb) {
141
117
  // Remove the previous identical callback
142
118
  this.unlisten(params, cb);
143
119
 
144
- const onMessage = (e) => {
145
- const data = JSON.parse(e.data);
120
+ const onMessage = (evt) => {
121
+ const data = JSON.parse(evt.data);
146
122
  let source = params.channel;
147
123
  source += params.args ? ` ${params.args}` : '';
148
124
 
@@ -164,6 +140,13 @@ class WebSocketConnector {
164
140
  return { onMessageCb: onMessage, onErrorCb: errorCb };
165
141
  }
166
142
 
143
+ /**
144
+ * Unlisten websocket messages.
145
+ *
146
+ * @param {Object} params Parameters for the websocket get request.
147
+ * @param {function} cb Callback used when listen.
148
+ * @private
149
+ */
167
150
  unlisten(params, cb) {
168
151
  this.subscriptions
169
152
  .filter((s) => {
@@ -180,31 +163,33 @@ class WebSocketConnector {
180
163
 
181
164
  /**
182
165
  * Subscribe to a given channel.
183
- * @private
166
+ *
184
167
  * @param {Object} params Parameters for the websocket get request
185
168
  * @param {function} cb callback on listen
186
169
  * @param {function} errorCb Callback on error
187
- * @param {boolean} quiet if subscribe should be quiet
170
+ * @param {boolean} quiet if false, no GET or SUB requests are send, only the callback is registered.
171
+ * @private
188
172
  */
189
- subscribe(params, cb, errorCb, quiet) {
173
+ subscribe(params, cb, errorCb, quiet = false) {
190
174
  const { onMessageCb, onErrorCb } = this.listen(params, cb, errorCb);
191
175
  const reqStr = WebSocketConnector.getRequestString('', params);
192
176
 
193
- if (!quiet) {
194
- const index = this.subscriptions.findIndex((subcr) => {
195
- return params.channel === subcr.params.channel && cb === subcr.cb;
196
- });
197
- const newSubscr = { params, cb, errorCb, onMessageCb, onErrorCb };
198
- if (index > -1) {
199
- this.subscriptions[index] = newSubscr;
200
- } else {
201
- this.subscriptions.push(newSubscr);
202
- }
177
+ const index = this.subscriptions.findIndex((subcr) => {
178
+ return params.channel === subcr.params.channel && cb === subcr.cb;
179
+ });
180
+ const newSubscr = { params, cb, errorCb, onMessageCb, onErrorCb, quiet };
181
+ if (index > -1) {
182
+ this.subscriptions[index] = newSubscr;
183
+ } else {
184
+ this.subscriptions.push(newSubscr);
203
185
  }
204
186
 
205
187
  if (!this.subscribed[reqStr]) {
206
- this.send(`GET ${reqStr}`);
207
- this.send(`SUB ${reqStr}`);
188
+ if (!newSubscr.quiet) {
189
+ this.send(`GET ${reqStr}`);
190
+ this.send(`SUB ${reqStr}`);
191
+ }
192
+
208
193
  this.subscribed[reqStr] = true;
209
194
  }
210
195
  }
@@ -216,27 +201,29 @@ class WebSocketConnector {
216
201
  * @private
217
202
  */
218
203
  unsubscribe(source, cb) {
219
- this.subscriptions
220
- .filter((s) => {
221
- return s.params.channel === source && (!cb || s.cb === cb);
222
- })
223
- .forEach(({ onMessageCb, onErrorCb }) => {
224
- this.websocket.removeEventListener('message', onMessageCb);
225
- if (onErrorCb) {
226
- this.websocket.removeEventListener('error', onErrorCb);
227
- this.websocket.removeEventListener('close', onErrorCb);
228
- }
229
- });
204
+ const toRemove = this.subscriptions.filter((s) => {
205
+ return s.params.channel === source && (!cb || s.cb === cb);
206
+ });
207
+
208
+ toRemove.forEach(({ onMessageCb, onErrorCb }) => {
209
+ this.websocket.removeEventListener('message', onMessageCb);
210
+ if (onErrorCb) {
211
+ this.websocket.removeEventListener('error', onErrorCb);
212
+ this.websocket.removeEventListener('close', onErrorCb);
213
+ }
214
+ });
230
215
 
231
216
  this.subscriptions = this.subscriptions.filter(
232
217
  (s) => s.params.channel !== source || (cb && s.cb !== cb),
233
218
  );
234
219
 
235
- // If there is no more subscriptions to this channel we DEL it.
220
+ // If there is no more subscriptions to this channel, and the removed subscriptions didn't register quietly,
221
+ // we DEL it.
236
222
  if (
237
223
  source &&
238
224
  this.subscribed[source] &&
239
- !this.subscriptions.find((s) => s.params.channel === source)
225
+ !this.subscriptions.find((s) => s.params.channel === source) &&
226
+ toRemove.find((subscr) => !subscr.quiet)
240
227
  ) {
241
228
  this.send(`DEL ${source}`);
242
229
  this.subscribed[source] = false;
@@ -22,22 +22,85 @@ describe('WebSocketConnector', () => {
22
22
  expect(server).toHaveReceivedMessages(['hello']);
23
23
  });
24
24
 
25
- test('#subscribe', async () => {
26
- // eslint-disable-next-line no-unused-vars
27
- const client = new Connector(`ws://foo:1234`);
28
- const params = {};
29
- const cb = jest.fn();
30
- client.subscribe(params, cb);
31
- expect(client.subscriptions.length).toBe(1);
32
- expect(client.subscriptions[0].params).toBe(params);
33
- expect(client.subscriptions[0].cb).toBe(cb);
25
+ describe('#subscribe', () => {
26
+ test('adds subscrption to subscriptions array', async () => {
27
+ // eslint-disable-next-line no-unused-vars
28
+ const client = new Connector(`ws://foo:1234`);
29
+ await server.connected;
30
+ const params = { channel: 'bar', args: ['baz'], id: 'id' };
31
+ const cb = jest.fn();
32
+ const errorCb = jest.fn();
33
+ client.subscribe(params, cb, errorCb);
34
+ expect(client.subscriptions.length).toBe(1);
35
+ expect(client.subscriptions[0].params).toBe(params);
36
+ expect(client.subscriptions[0].cb).toBe(cb);
37
+ expect(client.subscriptions[0].errorCb).toBe(errorCb);
38
+ expect(client.subscriptions[0].quiet).toBe(false);
39
+
40
+ const obj = { source: 'bar baz', client_reference: 'id' };
41
+ server.send(JSON.stringify(obj));
42
+
43
+ expect(cb).toHaveBeenCalledTimes(1);
44
+ expect(cb).toHaveBeenCalledWith(obj);
45
+ });
46
+
47
+ test("doesn't duplicate subscriptions", async () => {
48
+ // eslint-disable-next-line no-unused-vars
49
+ const client = new Connector(`ws://foo:1234`);
50
+ await server.connected;
51
+ const params = { channel: 'bar', args: ['baz'], id: 'id' };
52
+ const cb = jest.fn();
53
+ const errorCb = jest.fn();
54
+ client.subscribe(params, cb, errorCb, true);
55
+ client.subscribe(params, cb, errorCb, true);
56
+ expect(client.subscriptions.length).toBe(1);
57
+
58
+ const obj = { source: 'bar baz', client_reference: 'id' };
59
+ server.send(JSON.stringify(obj));
60
+
61
+ expect(cb).toHaveBeenCalledTimes(1);
62
+ expect(cb).toHaveBeenCalledWith(obj);
63
+ });
64
+
65
+ test('send GET and SUB requests.', async () => {
66
+ // eslint-disable-next-line no-unused-vars
67
+ const client = new Connector(`ws://foo:1234`);
68
+ client.send = jest.fn();
69
+ const params = { channel: 'bar', args: ['baz'], id: 'id' };
70
+ const cb = jest.fn();
71
+ const errorCb = jest.fn();
72
+ client.subscribe(params, cb, errorCb);
73
+ expect(client.send).toHaveBeenCalledTimes(2);
74
+ expect(client.send).toHaveBeenCalledWith('GET bar baz id');
75
+ expect(client.send).toHaveBeenCalledWith('SUB bar baz id');
76
+ client.send.mockRestore();
77
+ });
78
+
79
+ test('should register callback without sending GET and SUB requests (quiet=true).', async () => {
80
+ // eslint-disable-next-line no-unused-vars
81
+ const client = new Connector(`ws://foo:1234`);
82
+ await server.connected;
83
+ const params = { channel: 'bar', args: ['baz'], id: 'id' };
84
+ const cb = jest.fn();
85
+ const errorCb = jest.fn();
86
+ client.send = jest.fn();
87
+ client.subscribe(params, cb, errorCb, true);
88
+ expect(client.subscriptions.length).toBe(1);
89
+ expect(client.subscriptions[0].params).toBe(params);
90
+ expect(client.subscriptions[0].cb).toBe(cb);
91
+ expect(client.subscriptions[0].errorCb).toBe(errorCb);
92
+ expect(client.subscriptions[0].quiet).toBe(true);
93
+ expect(client.send).toBeCalledTimes(0);
94
+ client.send.mockRestore();
95
+ });
34
96
  });
35
97
 
36
98
  describe('#unsubscribe', () => {
37
- test('should only unsubscribe the subscription using the good cb', () => {
99
+ test('should only unsubscribe the subscription using the good cb', async () => {
38
100
  // eslint-disable-next-line no-unused-vars
39
101
  const client = new Connector(`ws://foo:1234`);
40
- const params = { channel: 'foo' };
102
+ await server.connected;
103
+ const params = { channel: 'foo', id: 'id' };
41
104
  const cb = jest.fn();
42
105
  const cb2 = jest.fn();
43
106
  client.subscribe(params, cb);
@@ -50,6 +113,12 @@ describe('WebSocketConnector', () => {
50
113
 
51
114
  client.unsubscribe('foo', cb);
52
115
  expect(client.subscriptions.length).toBe(1);
116
+
117
+ const obj = { source: 'foo', client_reference: 'id' };
118
+ server.send(JSON.stringify(obj));
119
+
120
+ expect(cb).toHaveBeenCalledTimes(0);
121
+ expect(cb2).toHaveBeenCalledTimes(1);
53
122
  });
54
123
 
55
124
  test('should unsubscribe all subscriptions related to a channel', () => {
@@ -75,46 +144,37 @@ describe('WebSocketConnector', () => {
75
144
  expect(client.subscriptions[0].params).toBe(params2);
76
145
  expect(client.subscriptions[0].cb).toBe(cb2);
77
146
  });
78
- });
79
147
 
80
- describe('#setBbox', () => {
81
- test.only('should remove subscriptions before re adding it ', () => {
148
+ test('send DEL when there is no more unquiet subscriptions on the channel', () => {
82
149
  // eslint-disable-next-line no-unused-vars
83
150
  const client = new Connector(`ws://foo:1234`);
151
+ client.send = jest.fn();
84
152
  client.websocket.removeEventListener = jest.fn();
85
153
  client.websocket.addEventListener = jest.fn();
86
- client.send = jest.fn();
87
154
  const params = { channel: 'foo' };
88
- const params2 = { channel: 'bar' };
89
155
  const cb = jest.fn();
90
- const cb2 = jest.fn();
91
- client.subscribe(params, cb);
92
156
  client.subscribe(params, cb);
93
- client.subscribe(params, cb);
94
- client.subscribe(params, cb2);
95
- client.subscribe(params2, cb2);
96
- expect(client.subscriptions.length).toBe(3);
97
- expect(client.websocket.removeEventListener).toBeCalledTimes(2);
98
- expect(client.websocket.addEventListener).toBeCalledTimes(5);
99
-
100
- client.websocket.removeEventListener.mockReset();
101
- client.websocket.addEventListener.mockReset();
157
+ expect(client.send).toHaveBeenCalledWith('GET foo');
158
+ expect(client.send).toHaveBeenCalledWith('SUB foo');
102
159
 
103
- client.setBbox([0, 0, 0, 0]);
160
+ client.unsubscribe('foo');
161
+ expect(client.send).toHaveBeenCalledWith('DEL foo');
162
+ });
104
163
 
105
- expect(client.subscriptions.length).toBe(3);
106
- expect(client.websocket.removeEventListener).toBeCalledTimes(3);
107
- expect(client.websocket.addEventListener).toBeCalledTimes(3);
164
+ test("doesn't send DEL when we unsubscribe a quiet channel", () => {
165
+ // eslint-disable-next-line no-unused-vars
166
+ const client = new Connector(`ws://foo:1234`);
167
+ client.send = jest.fn();
168
+ client.websocket.removeEventListener = jest.fn();
169
+ client.websocket.addEventListener = jest.fn();
170
+ const params = { channel: 'foo' };
171
+ const cb = jest.fn();
172
+ client.subscribe(params, cb, null, true);
173
+ expect(cb).toHaveBeenCalledTimes(0);
108
174
 
109
175
  client.unsubscribe('foo');
110
- expect(client.subscriptions.length).toBe(1);
111
- expect(client.subscriptions[0].params).toBe(params2);
112
- expect(client.subscriptions[0].cb).toBe(cb2);
113
- client.unsubscribe('bar');
114
- expect(client.subscriptions.length).toBe(0);
176
+ expect(cb).toHaveBeenCalledTimes(0);
115
177
  client.send.mockRestore();
116
- client.websocket.removeEventListener.mockRestore();
117
- client.websocket.addEventListener.mockRestore();
118
178
  });
119
179
  });
120
180
  });
@@ -13,7 +13,9 @@ import { v4 as uuid } from 'uuid';
13
13
  * @classproperty {string} key - Identifier of the layer. Must be unique.
14
14
  * @classproperty {string[]} copyrights - Array of copyrights.
15
15
  * @classproperty {boolean} isBaseLayer - Define if the layer is a base layer. Read-only.
16
- * @classproperty {boolean} isQueryable - Define if the layer can be queried. Read-only.
16
+ * @classproperty {boolean} isQueryable - Define if the layer can be queried. If false, it will set isHoverActive and isClickActive to false. Read-only.
17
+ * @classproperty {boolean} isClickActive - If true feature information will be queried on user click event. See inherited layers for more informations. Read-only.
18
+ * @classproperty {boolean} isHoverActive - If true feature information will be queried on pointer move event. See inherited layers for more informations. Read-only.
17
19
  * @classproperty {boolean} isReactSpatialLayer - Custom property for duck typing since `instanceof` is not working when the instance was created on different bundles. Read-only.
18
20
  * @classproperty {Layer[]} children - List of children.
19
21
  * @classproperty {boolean} visible - Define if the layer is visible or not.
@@ -33,9 +35,9 @@ export default class Layer extends Observable {
33
35
  * @param {Object} [options.properties={}] Application-specific layer properties.
34
36
  * @param {boolean} [options.visible=true] If true this layer is visible on the map.
35
37
  * @param {boolean} [options.isBaseLayer=false] If true this layer is a baseLayer.
36
- * @param {boolean} [options.isQueryable=true] If true this layer can be queried.
37
- * @param {boolean} [options.isClickActive=true] If true feature information will be queried on click event. See inherited layers for more informations.
38
- * @param {boolean} [options.isHoverActive=true] If true feature information will be queried on pointer move event. See inherited layers for more informations.
38
+ * @param {boolean} [options.isQueryable=true] Define if the layer can be queried. If false, it will also set isHoverActive and isClickActive to false. Read-only.
39
+ * @param {boolean} [options.isClickActive=true] If true feature information will be queried on click event. See inherited layers for more informations. Read-only.
40
+ * @param {boolean} [options.isHoverActive=true] If true feature information will be queried on pointer move event. See inherited layers for more informations. Read-only.
39
41
  * @param {number} [options.hitTolerance=5] Hit-detection tolerance in css pixels. Pixels inside the radius around the given position will be checked for features.
40
42
  */
41
43
  constructor(options = {}) {
@@ -109,10 +111,10 @@ export default class Layer extends Observable {
109
111
  value: !!isQueryable,
110
112
  },
111
113
  isClickActive: {
112
- value: !!isClickActive,
114
+ value: !!isQueryable && !!isClickActive,
113
115
  },
114
116
  isHoverActive: {
115
- value: !!isHoverActive,
117
+ value: !!isQueryable && !!isHoverActive,
116
118
  },
117
119
  hitTolerance: {
118
120
  value: hitTolerance || 5,
@@ -307,6 +309,7 @@ export default class Layer extends Observable {
307
309
  // eslint-disable-next-line no-console
308
310
  console.error(
309
311
  'getFeatureInfoAtCoordinate must be implemented by inheriting layers',
312
+ this.key,
310
313
  );
311
314
 
312
315
  // This layer returns no feature info.
@@ -427,7 +430,7 @@ export default class Layer extends Observable {
427
430
  .then((featureInfo) => {
428
431
  const { features, layer, coordinate } = featureInfo;
429
432
  this.hoverCallbacks.forEach((callback) =>
430
- callback({ features, layer, coordinate }),
433
+ callback(features, layer, coordinate),
431
434
  );
432
435
  return featureInfo;
433
436
  })
@@ -63,6 +63,21 @@ describe('Layer', () => {
63
63
  expect(layer.name).toEqual('Layer');
64
64
  });
65
65
 
66
+ test('should set isClickActive and isHoverActive to false if isQueryable is set to false.', () => {
67
+ const options = {
68
+ name: 'Layer',
69
+ isQueryable: false,
70
+ isClickActive: true,
71
+ isHoverActive: true,
72
+ olLayer,
73
+ };
74
+ const layer = new Layer(options);
75
+ expect(layer).toBeInstanceOf(Layer);
76
+ expect(layer.isQueryable).toBe(false);
77
+ expect(layer.isClickActive).toBe(false);
78
+ expect(layer.isHoverActive).toBe(false);
79
+ });
80
+
66
81
  test('should called terminate on initialization.', () => {
67
82
  const layer = new Layer({ name: 'Layer', olLayer });
68
83
  const spy = jest.spyOn(layer, 'terminate');
@@ -465,7 +480,13 @@ describe('Layer', () => {
465
480
  expect(spy).toHaveBeenCalledWith(evt.coordinate);
466
481
  expect(featureInfo).toBe(goodFeatureInfo);
467
482
  expect(fn).toHaveBeenCalledTimes(1);
483
+ expect(fn.mock.calls[0][0]).toBe(goodFeatureInfo.features);
484
+ expect(fn.mock.calls[0][1]).toBe(goodFeatureInfo.layer);
485
+ expect(fn.mock.calls[0][2]).toBe(goodFeatureInfo.coordinate);
468
486
  expect(fn2).toHaveBeenCalledTimes(1);
487
+ expect(fn2.mock.calls[0][0]).toBe(goodFeatureInfo.features);
488
+ expect(fn2.mock.calls[0][1]).toBe(goodFeatureInfo.layer);
489
+ expect(fn2.mock.calls[0][2]).toBe(goodFeatureInfo.coordinate);
469
490
  done();
470
491
  });
471
492
  });
@@ -599,7 +620,13 @@ describe('Layer', () => {
599
620
  expect(spy).toHaveBeenCalledWith(evt.coordinate);
600
621
  expect(featureInfo).toBe(goodFeatureInfo);
601
622
  expect(fn).toHaveBeenCalledTimes(1);
623
+ expect(fn.mock.calls[0][0]).toBe(goodFeatureInfo.features);
624
+ expect(fn.mock.calls[0][1]).toBe(goodFeatureInfo.layer);
625
+ expect(fn.mock.calls[0][2]).toBe(goodFeatureInfo.coordinate);
602
626
  expect(fn2).toHaveBeenCalledTimes(1);
627
+ expect(fn2.mock.calls[0][0]).toBe(goodFeatureInfo.features);
628
+ expect(fn2.mock.calls[0][1]).toBe(goodFeatureInfo.layer);
629
+ expect(fn2.mock.calls[0][2]).toBe(goodFeatureInfo.coordinate);
603
630
  done();
604
631
  });
605
632
  });